DEV Community

Cover image for Project 74 of 100 - Multipart Form
James Hubert
James Hubert

Posted on

Project 74 of 100 - Multipart Form

Hey! I'm on a mission to make 100 React.js projects. Please follow my dev.to profile or my twitter for updates and feel free to reach out if you have questions. Thanks for your support!

Link to today's deployed app: Link
Link to the repo: github

I was looking for a short project a week ago to continue my 100 projects list and came across a 20 minute Youtube tutorial that was mailed out as a part of the weekly FreeCodeCamp.org email link here. Of course buyer beware, anyone who promises you'll implement a feature in a short Youtube tutorial probably isn't helping build the entire project, and the video ends mostly handling data with pseudocode.

So last weekend I tried to implement this with the Context API, and then found it was so simple it wasn't even worth using Context, and instead just kept state and app-wide functions in the App component. Of course a real application wouldn't function so simply.

Basic Structure

The basic structure of the application places all importance on the App component. It didn't have to be App, it could have been a page component several layers deep, but the point is it's a parent component and the multiple steps in the form are handled simply as child components one level down.

Here is the JSX:

import React, {useState} from 'react'

function App() {
...
  return (
    <div className="App">
      <h1>
        React multi-part form
      </h1>
      <p>
        by <a href="https://twitter.com/jwhubert91">@jwhubert91</a>
      </p>
      <main className='main-form'>
        <FormPage page={page} data={data} setData={setData} />
        <button onClick={handleButtonClick}>{buttonText()}</button>
      </main>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In the App component I wanted to share a button between all pages so I kept it here in the parent component.

The child component was so simple that I knew what props I needed to pass down even before creating it. It would be a simple div where some logic would render a different input to the user depending on the page. I then pass down the data state variable and the setData handler so that we can build controlled inputs in the child components using state from the parent component.

Then I added state variables and handlers to the App component, above the JSX:

function App() {
  const [page,setPage] = useState(0)
  const [data,setData] = useState({
    email: '',
    username: '',
    color: '',
    spam: true
  })

  const handleButtonClick = () => {
    if (page === 4) {
      setPage(0)
    } else {
      setPage(prevPage => prevPage + 1)
    }
  }

  const buttonText = () => {
    if (page < 3) {
      return "next"
    } else if (page === 3) {
      return "submit"
    } else {
      return "edit"
    }
  }
return (...)
}
Enter fullscreen mode Exit fullscreen mode

Here we simply handle the button text and the page number as the user clicks through the form.

Child Form Component

Now, in the child component we need to render different code depending on what page is being passed through to props. Here I just extended what the method that was taught in the otherwise not very useful Youtube video above. We use conditional rendering by short circuiting the &&.

We can simply do this multiple times in a row and render whatever is called for by the page number. Pretty simple, I know, but it works well for this use case.

import React from 'react'

function FormPage({page,data,setData}) {
  return (
    <div>
      {page === 0 && 
        <input 
          type="text" 
          placeholder="Email"
          value={data.email}
          onChange={(e) => setData(prevData => ({
            ...prevData,
            email: e.target.value
          }))}
        />}
      {page === 1 && 
        <input 
          type="text" 
          placeholder="Username"
          value={data.username}
          onChange={(e) => setData(prevData => ({
            ...prevData,
            username: e.target.value
          }))}
        />}
      {page === 2 && 
        <input 
          type="text" 
          placeholder="Favorite color"
          value={data.color}
          onChange={(e) => setData(prevData => ({
            ...prevData,
            color: e.target.value
          }))}
        />}
      {page === 3 && 
        <div>
          <p>Do you want spam?
            <input 
                type="checkbox" 
                checked={data.spam}
                onChange={(e) => setData(prevData => ({
                  ...prevData,
                  spam: e.target.checked
                }))}
            />
          </p>
        </div>
      }
      {page === 4 && 
        <div className="results">
          <p><strong>email:</strong> {data.email}</p>
          <p><strong>username:</strong> {data.username}</p>
          <p><strong>favorite color:</strong> {data.color}</p>
          <p><strong>spam me?</strong> {data.spam ? "Yes" : "No"}</p>
        </div>
      }
    </div>
  )
}

export default FormPage
Enter fullscreen mode Exit fullscreen mode

For the last page, we simply render all of the user data like in a User Profile screen and ask the user if they want to edit, which takes them back to page 0.

That's it! Do you have a favorite way to implement multi-step forms, maybe using Context or Redux? Or even saving to the back-end with each step? Drop a comment below :)

If you like projects like this and want to stay up to date with more, check out my Twitter @jwhubert91, I follow back! See you tomorrow for another project.

Top comments (0)