Uploading file to server with React-hooks, Redux and Multer: PART 1

Samuel Lucas
Road to fullstack(MERN)
6 min readSep 19, 2020

--

I started building a portfolio project(book uploads) which started well until I got to the part where I wanted to implement an image and text upload at the front-end part. It took me several hours of googling, and in-person help to finally get it done.

One of the problems I encountered was that there aren’t enough resources(even tho’ I found none) for file and text uploading using react-redux, and that’s why I came up with the idea of writing a doc on it for those who need it.

I will not be explaining how to setup multer in this blog, but I’ll give a video reference(which I learnt from), made by Max(Acadmind). Learn the back-end aspect from it(tweak it to your taste) and let’s move on.

I’m sure you have setup your react form component and if you haven’t, feel free to copy from my code.

All underlined texts are links to resources where you can read more.

import React, { Fragment, useState } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { createBook } from "../../actions/collection";
const CreateBook = ({ createBook, history }) => {
...
}
CreateBook.propTypes = { createBook: PropTypes.func.isRequired};export default connect(null, { createBook }(withRouter(CreateBook));const [formData, setFormData] = useState({
title: " "
author: " "
});
const [image, setImage] = useState("");
const [imageName, setImageName] = useState("Choose file");
const { title, author } = formData;const onFileChange = (e) => {
setImage(e.target.files[0]);
setImageName(e.target.files[0].name);
};
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = (e) => {
e.preventDefault();

const payload = new FormData();
payload.append("bookImage", image);
payload.append("title", formData.title);
payload.append("author", formData.author);
createBook(payload, history);
};

So, I first created a state for my text input, then for my image itself, then next is the state to hold the image name(example: json.png ). The following line, I extracted the text input value(title, author, etc) from the state by destructing formData so that I can use them anywhere in my component.

Next, I needed to access the image file, so I used file API added to DOM in HTML5. The e.target.files[0] is to get the file a user selected, while the e.target.files[0].name is to get the name of the selected file. So I set the state of image I created(initially empty) to the image user selected, and the filename state(initially Choose file) to the file name of that file the user selected, as simple as that. Then I ensured they are inside the function onFileChange which I want to call when a user clicks on the file-input(html, as you will see.)

Next, I needed a way to save the user’s input into my state, so the logic behind this is that I set the state(originally empty strings) to the user’s input. To further understand this, let’s use the onChange handler to explain this.

setFormData({ ...formData, [e.target.name]: e.target.value });

What’s going on here is this: I’m setting the state of the formData setFormData to, first the value of the state before any update by the user, this is done by using the javascript spread operator and this will copy everything initially in the state, then secondly we want to update that copied state with the user input(whose logic I will explain later on). The reason why we are copying the formData and updating the copied state data rather than using push or concat is because state in React are immutable(they cannot be updated directly), so we need to copy the data in the state(formData in our case) into a setter I’ll say(setFormData) which is where we can tamper(update, delete…) with the data. Read more on setting state

Following is the onSubmit function, which handles user data upon submission. This serves as a form action. The first line of code there e.preventDefault() was added to prevent the data from refreshing(going empty) upon submission. In other cases, what it does is just to prevent the original action of an event from occurring. Now, what is the next line all about? This is where the real action begins.

I’ll try to explain what FormData() is as basic as I can. Basically, when you have a form that contains both text and file input, you want to use it, why? This is because it allows you to set the value of an input to the value of its state. To get a better explanation, you can read more from MDN.

It is needed for you to know that when I was creating a payload.append for the image file, you’ll notice that we don’t have bookImage defined anywhere in our component, right? That’s actually because it isn’t, we have defined in our back-end. How? If you remember, whenever we use multer, we always have upload.single when attaching the image to the router.post or app.post based on which you are using. So within my router.post, I had upload.single, and I passed a parameter of bookImage within it. As in

router.post("/addBook", upload.single("bookImage"),...)

The next line createBook() is a redux action, which will be discussed in the second part of this blog. All I’m doing within it is passing the payload variable created(which holds the action within new FormData() ) and history(also from redux). Now for the form.

return (
<Fragment>
<form onSubmit={ (e) => onSubmit(e) }
encType="multipart/form-data">
<input type="file" onChange={(e) => onFileChange(e)}
accept="image/*" multiple />

<label htmlFor="title" > Title </label>
<input type="text" placeholder="Title here" name="title"
value={title} onChange={(e) => onChange(e)} />
<input type="text" placeholder="Author here" name="author"
value={author} onChange={(e) => onChange(e)} />
<button> Create Book </button> </form>
</Fragment>
);
};

Fragment, a common pattern in React is for a component to return multiple elements. It can be used as a parent wrapper instead of div(in some cases), to better understand why and where you should use it, please reference the docs from React website

Within the form opening tag, we are saying that upon form submission, we want you to use the onSubmit function logic which we created earlier to process the submitted data. The enctype attribute specifies how the form-data should be encoded when submitting it to the server. And the multipart/form-data
means no characters are encoded. This value is required when you are using forms that have a file upload control

We are telling our first input to accept a file, and whenever there is a change(when the user selects a file), we need you to trigger the actions within the onChange function handler.

For the next two input, we are setting it to accept text as its input type. For the name and value they must be the same because that is what we will use to update the state data(setFormData), and for the onChange, whenever there is a change(when the user input a text), we trigger the actions within the onChange function handler. Now, the reason why we have the name and the value in these two input each(title, author) the same is because of ease and simplicity brought alive by [e.target.name]: e.target.value. What this means is that instead of the traditional way of setting what the user entered to the value manually, and then setting the state to the value(which now holds the user input), we can simply attach a similar name to the state, name and value, and then attach the value to the name, which is then added to the state.

Next up is the final part of the section, the prop-types and export. The prop-types is where we define of what type createBook is (could be boolean, function, object…).

CreateBook.propTypes = { createBook: PropTypes.func.isRequired };export default connect(null, { createBook }(withRouter(CreateBook));

The last part export uses connect which is a redux callback function, you can read more about it.

The PART 2 of this book will be talking about the redux aspect for this project.

Thank you for taking your time to read through this post. If you found it helpful, please give it a clap. Bye

--

--

Samuel Lucas
Road to fullstack(MERN)

Writer and developer based in Nigeria. On Medium, I write about JavaScript and web development 💻