skip to Main Content

I am using React and useState to capture my HTML form’s values. That seems to be working just fine. I have set default values for my user state so when I submit to the Express API on the backend it should be able to use those values stored in user state. Problem is after typing in values into the form, the first time it submits, it uses the default values, the second time I submit it works. So the values typed into the form, don’t actually get used until the second submit. Here is my code:

import { useState, useEffect } from "react";

const USER_DEFAULT = {
  fav_color: "",
  dist: "",
  origin: "",
  min_age: "",
  max_age: "",
};

function UserForm() {
  const [user, setUser] = useState(USER_DEFAULT);
  const [users, setUsers] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch(`http://localhost:1234/users/`)
        .then((res) => res.json())
        .then((data) => setUsers(data))
        .catch(console.log);

      return response;
    }
    fetchData();
  }, []);

  const handleChange = (event) => {
    const newUser = { ...user };
    newUser[event.target.name] = event.target.value;
    setUser(newUser);
    console.log(newUser);
  };

  const getUsers = (event) => {
    event.preventDefault();

    let fetchUrl = "http://localhost:1234/users?";

    if (user.fav_color) fetchUrl += `fav_color=${user.fav_color}&`;
    if (user.dist) fetchUrl += `dist=${user.dist}&`;
    if (user.origin) fetchUrl += `origin=${user.origin}&`;
    if (user.min_age) fetchUrl += `min_age=${parseInt(user.min_age)}&`;
    if (user.max_age) fetchUrl += `max_age=${parseInt(user.max_age)}&`;

    console.log(fetchUrl); // fetchUrl is getting set correctly and seems to be working fine

    fetch(fetchUrl)
      .then((response) => response.json())
      // this set doesn't work with the api call until I submit the form a 2nd time
      .then((data) => setUsers(data))
      .catch(console.log);
  };

  return (
    <div className="container text-center">
      <div className="row">
        <div className="col-3 pr-5">
          <form onSubmit={getUsers}>
            <h4 className="mb-4">Find Users!</h4>

            <div className="form-group">
              <label htmlFor="fav_color">Favorite Color:</label>
              <input
                id="fav_color"
                name="fav_color"
                type="text"
                className="form-control"
                value={user.fav_color}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="row">Distance:</label>
              <input
                id="dist"
                name="dist"
                type="number"
                className="form-control"
                value={user.dist}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="origin">Origin (Long, Lat):</label>
              <input
                id="origin"
                name="origin"
                type="text"
                className="form-control"
                value={user.origin}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="min_age">Minimum Age:</label>
              <input
                id="min_age"
                name="min_age"
                type="number"
                className="form-control"
                value={user.min_age}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="row">Maximum Age:</label>
              <input
                id="max_age"
                name="max_age"
                type="number"
                className="form-control"
                value={user.max_age}
                onChange={handleChange}
              />
            </div>
            <div className="mt-4">
              <button className="btn btn-success mr-2" type="submit">
                <i className="bi bi-file-earmark-check"></i> Search
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
}

export default UserForm;

I know this has something to do with setState not setting the values immediately after submitting the form. The changes are queued up and won’t take place until after the next page render correct? I also have read that I need to use an useEffect hook to get this to work but I have no idea how. I’ve tried one at the top of the code but it only sets the initial default values of the users state. I know there are a ton of answers out there for this issue but none of them really made sense to me. I couldn’t see how to use an useEffect hook here to make the state values update before I submit the form, not afterwards. Thanks for any help you can give!

2

Answers


    1. Depending on how fast you can update fields, you may run into race conditions with the way you’re updating the user state. You should use the functional update form instead
    2. There’s much easier ways to construct URL query parameters

    For example, you could create a single function to fetch users with or without query parameters

    const fetchUsers = async (params = {}) => {
      const nonEmptyParams = Object.fromEntries(
        Object.entries(params).filter(([_, val]) => val !== "")
      );
    
      const url = `http://localhost:1234/users?${new URLSearchParams(
        nonEmptyParams
      )}`;
    
      const res = await fetch(url);
      if (!res.ok) {
        throw new Error(`fetchUsers failed: ${res.status}`);
      }
      return res.json();
    };
    

    then use that in your component, both in the effect hook and the getUsers function

    function UserForm() {
      const [user, setUser] = useState(USER_DEFAULT);
      const [users, setUsers] = useState([]);
    
      useEffect(() => {
        fetchUsers().then(setUsers).catch(console.error);
      }, []);
    
      const handleChange = ({target: { name, value }}) => {
        // functional update
        setUser((prev) => ({
          ...prev,
          [name]: value
        }));
      };
      
      const getUsers = (event) => {
        event.preventDefault();
        
        fetchUsers(user).then(setUsers).catch(console.error);
      }
      
      return (
        <div className="container text-center">
          ...
        </div>
      );
    }
    

    Finally, in order to see any changes to the users state, you should render it somehow

    <ul>
      {users.map((u, i) => (
        <li key={i}>
          <pre>{JSON.stringify(u)}</pre>
        </li>
      ))}
    </ul>
    
    Login or Signup to reply.
  1. Try to not use async/await and .then/.catch at the same time. When replicated this code, it seems to work for me. You can check here
    https://codesandbox.io/s/jovial-rubin-k13772?file=/src/UserForm.js:0-3506

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search