Expand your app with image uploads

Lawrence Thomas
Oct 9 · 6 min read
Photo by SHTTEFAN on Unsplash

Have you been trying to build a blog website — or just a CRUD website — using the MERN stack? Have you wanted to include an image upload to your application and then display the image that was uploaded to your database back to the client?

Whew, that was a lot. But today I will show you how to do that using Cloudinary along with Multer.


Cloudinary

What is Cloudinary, you ask?

Cloudinary is a cloud service that lets you easily upload images to their cloud — which will then give you back an URL that we’ll save to our database and display back to the client.

It’ll make more sense once we put it into action. Head over to www.cloudinary.com, and create an account. Once finished, make sure you take down these credentials:

Cloud Name, API Key, API Secret

These will be visible on your dashboard. We will need these later when setting up our back end.


Create React App

All right, let’s start with creating our front end.

For this, we will use create-react-app to bootstrap a React starter project for us. By using create-react-app, we’ll not have to worry about setting up webpack or Babel (as create-react-app sorts this all out by default).

If you don’t already have create-react-app installed globally on your machine, you need to enter …

npm install -g create-react-app

… into your terminal. Then in your terminal, enter the following command:

npm install create-react-app cloudinary-app

cloudinary-app is going to be the name of your project — you can name it anything you want.

Once that is finished, we need to go into the project folder. To do that, enter cd cloudinary-app in the terminal.

While in the project folder, enter the following command into the terminal: npm start. This will spin up our development server and open your project on localhost:3000 in your browser.

Since we’re already working on our front end, let’s install the libraries we need. We’ll need Axios on the front end to make GET/POST requests to our back end. We’ll also use Redux (even though this is a small application, and we would be okay to just use the local state in React).

I know most people would like to include this in a larger project, so I will use Redux for state management. To do that, enter the following command into your terminal.

npm install axios react-redux redux redux-thunk --save

Let’s also create a few files. These aren’t necessary, but for following along with the tutorial, they are to keep it simple.

Inside the src folder, create a history.js file, and copy the following code:

import { createBrowserHistory } from “history”;export default createBrowserHistory();

The history file will create a history object into each route as a prop. This gives us full control over the browser history. This is helpful with redirecting the user after an action, in our case.

Now the next file we’ll create is a proxy file. This will make our HTTP requests to our server easier. Let’s first create a components folder inside the src folder. Inside the components folder, create an AxiosAPI.js file, and paste the following code:

import axios from “axios”;       export default axios.create({       baseURL: “http://localhost:5000"          });

If you ever had to make HTTP requests, you know typing out the long URL is a pain, so this makes it a lot easier. We will come back to our components folder in a little bit.


Index.js → Redux Store Setup

Now let’s connect our Redux store.

First, let’s go to the Index.js file already generated by CRA. In that file, replace the code with the following code. The Redux store is where all of our states are going to be stored.

import React from “react”;import ReactDOM from “react-dom”;import “./index.css”;import App from “./App”;import { Provider } from “react-redux”;import { createStore, applyMiddleware, compose } from “redux”;import reducers from “./reducers”;import reduxThunk from “redux-thunk”;const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;    const store = createStore(reducers,    composeEnhancers(applyMiddleware(reduxThunk)));ReactDOM.render(<Provider store={store}>
<App />
</Provider>,
document.getElementById(“root”));

Now, we need to create our reducers folder and our actions folder.

Under the src folder, create a reducers folder and an actions folder. Let’s first create our actions. Inside the actions folder, create a new file named imageActions.js. Inside this file, we will create our actions that will be sent to our reducer to tell Redux how to update the state of our application.

Actions

Before we create our action creators, let’s make our types.js file. Inside the actions folder create a types.js file. This is not mandatory, but it’s always a good practice to do this in case you ever make a typo when creating your action creators.

Flip over to the imageActions.js file. This is where we’ll create our actions file.

import { ADD_IMAGE, GET_IMAGES, GET_ERRORS } from “./types”;import AxiosAPI from “../components/AxiosAPI”;import history from “../history”;   //ADD IMAGE    export const addImage = imageData => dispatch => {
AxiosAPI.post(“/add”, imageData).then(res => dispatch({
type: ADD_IMAGE,
payload: res.data
})).catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}));
history.push(“/”);
};
//GET ALL IMAGES export const getAllImages = () => dispatch => {
AxiosAPI.get(“/”).then(res => dispatch({
type: GET_IMAGES,
payload: res.data
})).catch(err => dispatch({
type: GET_IMAGES,
payload: null
}));
};

The imageData being passed in is the formData that we’ll be using later in the form component, which is going to be the title and the image.


Reducers

Now let’s set up our reducer.

Since this is a small application, we’ll only have one reducer. But as many of you are probably creating larger applications, you’ll more than likely have more than one reducer and will have to use combineReducers on your rootReducer file.

Anyway, in our reducers folder, we can go ahead and create an index.js file. Reducers are just plain JavaScript objects.

So add the following code inside the index.js file. It’ll also be helpful if you are using Redux DevTools as well to physically see your data in the Redux store. Copy the following code into the imageActions.js file.

import { ADD_IMAGE, GET_IMAGES } from “../actions/types”;     const initialState = {
images: []
};
export default function(state = initialState, action) {
switch (action.type) {
case ADD_IMAGE:
return {…state, images: [action.payload, …state.images]};
case GET_IMAGES:
return { …state, images: action.payload };
default:
return state;
}
}

Components

Let’s start with the form we’ll need to use connect to wire up our action creator to the form component.

Create a file named Form.js in the components folder.

import React from “react”;import { connect } from “react-redux”;import { addImage } from “../actions/imageActions”;   class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: “”,
image: “”
};
this.onChangeTitle = this.onChangeTitle.bind(this);
this.onChangeImage = this.onChangeImage.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChangeTitle = e => {
this.setState({ title: e.target.value });
};
onChangeImage = e => {
this.setState({ image: e.target.files[0] });
};
onSubmit(e) {
e.preventDefault();
let formData = new FormData();
formData.append(“title”, this.state.title);
formData.append(“image”, this.state.image);
this.props.addImage(formData);

this.setState({
title: “”,
image: “”
});
}
render() {
return (
<div className=”form-container”>
<form encType=”multipart/form-data” onSubmit={this.onSubmit}>
<h2>Image Form</h2>
<label className=”form-label”>Image Title</label>
<input
className=”form-input”
placeholder=”Enter Image Title”
type=”text”
value={this.state.title}
onChange={this.onChangeTitle}
/>
<label className=”form-label”>Choose an Image</label>
<input type=”file”
className=”form-input”
onChange={this.onChangeImage} />
<button type=”submit” className=”submit-btn”>Submit!</button>
</form>
</div>
);
}
}
export default connect(null,{ addImage })(Form);

The reason we have null as the first argument in the connect function is because we are not passing anything in. Usually we would pass mapStateToProps, as you will see in the AllImages.js file.

Here is what our Form should look like now!

Create another file under components named AllImages.js. This component is responsible for displaying the data we’re sending from the form to the server. Then we are taking that data that is coming back from the server and displaying it in this file. Hopefully that makes sense. Here is the code for AllImages.js:

AllImages.js

All of our images are being stored in an array. We’re using the action creator getAllImages to pull out the data from the array. We do this by mapping over the array, and it will return a single image. The mapStateToProps does exactly what it says: maps the state to props that we can use in our component.

That’s it for the front end of our application. Now we’ll have to set up our back end, which we’ll do in the second part of this article.

View Part 2 of this article.

Here is a link to the GitHub repository.

Better Programming

Advice for programmers.

Lawrence Thomas

Written by

NYC Based Web Developer Twitter: @_law92 Instagram: @_law92

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade