How to update and display first non-empty value on setState with react?

Viewed 632

I'm trying to display the value of my inputs from a from, in a list. Everytime I hit submit, I expect that it should display the inputs in order.

The problem I'm having is that when I try to submit my form and display inputs in a list, it display an empty value first. On the next submit and thereafter, it displays the previous value, not the new one on the input field.

There's also an error message but i'm not understanding how to relate it to the problem. It's a warning message regarding controlled/uncontrolled components.

I've tried to add if statements to check for empty values in each functions but the problem persists.I've tried to manage the error massage by being consistent with all input to be controlled elements using setState, but nothing works.

I looked through todo list examples on github. I guess i'm trying to keep it in one functional component versus multiple ones, and I'm not using class components. I tried to follow the wesbos tutorial on Javascript 30 day challenge, day 15: Local Storage and Event Delegation. I'm trying to use React instead of plain JS.

Here's what my component looks like.

import React, { useEffect, useState } from "react";
import "../styles/LocalStorage.css";

export const LocalStorage = () => {
  const [collection, setCollection] = useState([]);
  const [value, setValue] = useState();
  const [item, setItem] = useState({ plate: "", done: false });
  const [display, setDisplay] = useState(false);

  //set the value of the input
  const handleChange = (e) => {
    if (e.target.value === "") return;
    setValue(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    if (value === "" || undefined) return;
    setItem((prevState) => {
      return { ...prevState, plate: value };
    });

    addItem(item);
    setDisplay(true);
    setValue("");
  };

  const addItem = (input) => {
    if (input.plate === "") return;
    setCollection([...collection, input]);
  };

  return (
    <div>
      <div className="wrapper">
        <h2>LOCAL TAPAS</h2>

        <ul className="plates">
          {display ? (
            collection.map((item, i) => {
              return (
                <li key={i}>
                  <input
                    type="checkbox"
                    data-index={i}
                    id={`item${i}`}
                    checked={item.done}
                    onChange={() =>
                      item.done
                        ? setItem((state) => ({ ...state, done: false }))
                        : setItem((state) => ({ ...state, done: true }))
                    }
                  />
                  <label htmlFor={`item${i}`}>{item.plate}</label>
                </li>
              );
            })
          ) : (
            <li>Loading Tapas...</li>
          )}
        </ul>
        <form className="add-items" onSubmit={handleSubmit}>
          <input
            type="text"
            name="item"
            placeholder="Item Name"
            required
            value={value}
            onChange={handleChange}
          />
          <button type="submit">+ Add Item</button>
        </form>
      </div>
    </div>
  );
};
1 Answers

Since the setState function is asynchronous, you cannot use the state value item right after you fire the setItem(...). To ensure you get the latest value for your addItem function:

setItem((prevState) => {
  const newItem = { ...prevState, plate: value };
  addItem(newItem); // Now, it's getting the updated value!
  return newItem;
});

And regarding the controlled and uncontrolled components, you can read the docs about it here. To fix your problem, you can initialize the value state with an empty string:

const [value, setValue] = useState('');
Related