4

I'm currently experiencing an issue pushing the input field value to state onSubmit.

codesandbox

I'm trying to set an input field value to the state so that I can use that value once the component is updated to redirect the user to another page. I tested the path manually and it works, but since the state is not updating synchronously, the redirect does not work. I can render the input value on the page, but if I try to log it, it long undefined(for the first time) and the previous state on a second submit.

import React, { useRef, useState } from "react";
import { db } from "../firebase";
import { Redirect } from "@reach/router";

function CreateProject(props) {
  const [id, setID] = useState(null);
  const colorRef = useRef(null);
  const projectNameRef = useRef(null);

  const handleSubmit = e => {
    e.preventDefault();
    const project = {
      name: projectNameRef.current.value,
      colors: [colorRef.current.value],
      colorName: colorNameRef.current.value,
      createdAt: new Date()
    };
    setID(projectNameRef.current.value);

    db.collection("users")
      .doc(`${props.user}`)
      .collection("projects")
      .doc(`${projectNameRef.current.value}`)
      .set({ ...project });
    e.target.reset();
  };


  return id ? (
    <Redirect from="/projects/new" to={`projects/:${id}`} noThrow />
  ) : (
    <div>
      <div>
        <h1>Create new selection</h1>
        <form onSubmit={handleSubmit}>
          <label>Color</label>
          <input ref={colorNameRef} type="text" name="colorName" />
          <label>Project Name</label>
          <input ref={projectNameRef} type="text" name="projectName" required />
          <button type="submit">Submit</button>
        </form>
      </div>
    </div>
  );
}

export default CreateProject;

react: 16.8.6

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Miloslavc
  • 127
  • 2
  • 7

2 Answers2

2

That's the way react hooks useState works, to do something after a state change you should perform it inside a useEffect hook, like the following:

useEffect(() => {
  if (id) {
    console.log(id);
    projectNameRef.current.value = ''
  }
}, [id])

This effect will run every time the id value changes (and in the first render) so you could add your logic there and perform your desired actions based on the state change.

bntzio
  • 1,274
  • 14
  • 27
  • When mutating the state with `useState`, react won't wait for it to continue, it's asynchronous, so that's why listening to state changes with a `useEffect` hook is the way to go with react 16. – bntzio Apr 25 '19 at 17:57
  • [More info about the `useEffect` hook](https://reactjs.org/docs/hooks-effect.html). – bntzio Apr 25 '19 at 17:57
  • Thanks :) , after refactoring and adding useEffect it run properly but it didn't redirect where it was supposed to, and after lots of attempts I figured that I had an error in the redirect, where I was redirecting to={`projects/:${id}`} instead of to={`projects/${id}`} – Miloslavc Apr 26 '19 at 10:31
2

I think your use of ref here is inappropriate and may be causing the issue. I would rewrite your function like this.

function CreateProject() {
  const [id, setID] = useState(null);
  const [shouldRedirect, setShouldRedirect] = useState(false);

  const handleSubmit = e => {
    e.preventDefault();
    setShouldRedirect(true);
  };

  const handleChange = (e) => {
    setID(e.target.value);
  }

  return shouldRedirect ? (
    <Redirect from="/projects/new" to={`projects/:${id}`} noThrow />
  ) : (
    <div>
      <div>
        <h1>Create new selection</h1>
        <form onSubmit={handleSubmit}>
          <label>Project Name</label>
          <input onChange={handleChange} type="text" name="projectName" required />
          <button type="submit">Submit</button>
        </form>
      </div>
    </div>
  );

In this way your state is always being updated and therefore so is your redirect URL. When you submit, you simply tell the component it should now submit with the current ID.

You can see how this works from the React documentation.

You may even be able to replace the conditional render with a functional call to history.push using withRouter. See advice on this question.

Cameron Downer
  • 1,839
  • 17
  • 26