Search code examples
reactjsreact-reduxreact-hooksreducersuse-reducer

How can i set the initialState value from the backend to use in form input value? Input doesn't change after giving value = product.name from Backend


Right now I am using useEffect() to check for the product via the id param. And I can set initialState value to an empty string, but is there any way that we can set the initialState value via the backend or after the useEffect() has been done. If I am using value={product.name} in <input/> then the value is there in the Input field, but it never changes. Please Help.

  const reducer = (state, { type, payload }) => {
  switch (type) {
    case "name":
      return { ...state, name: { value: payload } };
    case "desc":
      return { ...state, desc: { value: payload } };
    case "price":
      return { ...state, price: { value: payload } };
    case "img":
      return { ...state, img: { value: payload } };
    case "isFeatured":
      return { ...state, isFeatured: { value: payload } };

    default:
      return { state };
  }
};
const initialState = {
  name: { value: "" },
  desc: { value: "" },
  price: { value: "" },
  img: { value: "" },
  isFeatured: { value: false },
};

Product Component

 const [state, dispatch] = useReducer(reducer, initialState);

  const [showForm, setShowForm] = useState(false);   const [error, setError] = useState("Invalid");   const [product, setProduct] = useState();   const id = useParams().id;   useEffect(() => {
    const fetchHandler = async () => {
      await fetch(`http://localhost:5000/products/${id}`)
        .then(async (res) => await res.json())
        .then((p) => setProduct(p.product))
        .catch((err) => setError(err));
    };
    fetchHandler();   }, [id]);

InputChangeHandler

   const inputChangeHandler = (e, name) => {
    switch (name.toString()) {
      case "name":
        dispatch({ type: "name", payload: e.target.value });
        break;
      case "price":
        dispatch({ type: "price", payload: e.target.value });
        break;
      case "desc":
        dispatch({ type: "desc", payload: e.target.value });
        break;
      case "img":
        dispatch({ type: "img", payload: e.target.value });
        break;
      case "isFeatured":
        dispatch({ type: "isFeatured", payload: Boolean(e.target.checked) });
        break;
      default:
        break;
    }
  };

My Form

{showForm && (
    <form onSubmit={submitForm}>
      <FormControl>
        <TextField
          placeholder={product.name}
          value={product.name}
          onChange={(e) => inputChangeHandler(e, "name")}
          margin="dense"
          className="diff"
          id="outlined-basic"
          label="Name"
          variant="outlined"
        />
        <TextField
          multiline
          placeholder={product.description}
          value={state.desc.value}
          onChange={(e) => inputChangeHandler(e, "desc")}
          id="outlined-basic"
          margin="dense"
          label="Description"
          variant="outlined"
        />
        <TextField
          placeholder={product.price}
          value={state.price.value}
          onChange={(e) => inputChangeHandler(e, "price")}
          margin="dense"
          id="outlined-basic"
          label="Price"
          variant="outlined"
        />
        <TextField
          placeholder={product.image}
          value={product.image}
          onChange={(e) => inputChangeHandler(e, "img")}
          margin="dense"
          id="outlined-basic"
          label="Image"
          variant="outlined"
        />

        <Checkbox
          value={state.isFeatured.value}
          onChange={(e) => inputChangeHandler(e, "isFeatured")}
          name="checkedB"
          color="primary"
        />
        <Button type="submit"> Update</Button>
        <FormHelperText id="my-helper-text">Update</FormHelperText>
      </FormControl>
    </form>

Solution

  • I can think of two options when dealing with async data in combination with a form in your situation.

    The first option is to load the data before rendering the form component. Then pass the data down to the form component as the initialState. The drawback of this solution is that the form is completely hidden while loading the data.

    The second option is to add an action to your reducer which updates more than a single field. You can dispatch this action after loading the product data and update all fields according to the updated data.

    You can simplify the reducer by adding the field name to the payload.

    const reducer = (state, { type, payload }) => {
      switch (type) {
        case "change":
          return { ...state, [payload.name]: { value: payload.value } };    
        case "productLoaded":
          return { ...state, ...payload };
        default:
          return { state };
      }
    };
    
    const inputChangeHandler = (e, name) => {
      const value = e.target.type === 'checkbox' ? Boolean(e.target.checked) : e.target.value;
      dispatch({ type: "change", payload: { name, value } });
    };
    
    useEffect(() => {
      const fetchHandler = async () => {
        await fetch(`http://localhost:5000/products/${id}`)
          .then(async (res) => await res.json())
          .then((p) => {
            setProduct(p.product);
            dispatch({ type: "productLoaded", payload: p.product });
          })
          .catch((err) => setError(err));
      };
      fetchHandler();
    }, [id]);