简体   繁体   中英

onChange event doesn't fire on first input

I'm new to React and I've got this registration (parent component) where it has an inventory (child component) and I'm changing the state of the inventory once the user inputs the quantity of the item that they hold so I can register them

But onChange seems to be delayed, I input 4 water bottles for example, and it console logs me the default value, only when I input the amount for another item like food it displays me the 4 water bottles and 0 food:(

This is what I'm working with...

Child component:

import React from "react";
import { Col, Row, FormGroup, Label, Input } from "reactstrap";

import waterIcon from "./../assets/water.png";
import soupIcon from "./../assets/food.png";
import medsIcon from "./../assets/aid.png";
import weaponIcon from "./../assets/gun.png";

function Inventory({ onSubmitInventory, currentInventory }) {
  const onChangeWater = (e) => {
    const newInventory = { ...currentInventory, water: e.target.value };
    onSubmitInventory(newInventory);
  };

  const onChangeSoup = (e) => {
    const newInventory = { ...currentInventory, soup: e.target.value };
    onSubmitInventory(newInventory);
  };

  const onChangeMeds = (e) => {
    const newInventory = { ...currentInventory, meds: e.target.value };
    onSubmitInventory(newInventory);
  };

  const onChangeWeapon = (e) => {
    const newInventory = { ...currentInventory, weapon: e.target.value };
    onSubmitInventory(newInventory);
  };

  return (
    <FormGroup>
      <Row className="justify-content-center text-center border-top pt-3">
        <Col xs="3">
          <img src={waterIcon} alt="Water" />
          <Label for="inventoryWater">Fiji Water:</Label>
          <Input
            type="number"
            name="inventoryWater"
            id="inventoryWater"
            placeholder="Enter amount..."
            onChange={onChangeWater}
          />
        </Col>
        <Col xs="3">
          <img src={soupIcon} alt="Soup" />
          <Label for="inventorySoup">Campbell Soup:</Label>
          <Input
            type="number"
            name="inventorySoup"
            id="inventorySoup"
            placeholder="Enter amount..."
            onChange={onChangeSoup}
          />
        </Col>
        <Col xs="3">
          <img src={medsIcon} alt="Aid" />
          <Label for="inventoryMeds">First Aid Pouch:</Label>
          <Input
            type="number"
            name="inventoryMeds"
            id="inventoryMeds"
            placeholder="Enter amount..."
            onChange={onChangeMeds}
          />
        </Col>
        <Col xs="3">
          <img
            className="d-block"
            style={{ margin: "0 auto" }}
            src={weaponIcon}
            alt="Gun"
          />
          <Label for="inventoryWeapon">AK47:</Label>
          <Input
            type="number"
            name="inventoryWeapon"
            id="inventoryWeapon"
            placeholder="Enter amount..."
            onChange={onChangeWeapon}
          />
        </Col>
      </Row>
    </FormGroup>
  );
}

export default Inventory;

Parent component:

import React, { useState } from "react";
import { Col, Row, Button, Form, Label, Input } from "reactstrap";
import { useForm } from "react-hook-form";

import Inventory from "./Inventory";
import MapContainer from "./MapContainer";

function Register() {
  const [lonlat, setLonlat] = useState("");
  const onMarkerChange = (lonlat) => {
    setLonlat(lonlat);
  };

  const [inventory, setInventory] = useState({
    water: 0,
    soup: 0,
    meds: 0,
    weapon: 0,
  });
  const onSubmitInventory = (newInventory) => {
    setInventory(newInventory);
    console.log(inventory);
  };

  const { register, handleSubmit, errors } = useForm();
  const onSubmit = (data) => {
    console.log(inventory);
    const requestOptions = {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: data.personName }),
    };
    console.log(data);
  };

  return (
    <Form
      id="registerForm"
      name="registerForm"
      onSubmit={handleSubmit(onSubmit)}
    >
      <h4>New Person</h4>
      <Row className="border-top pt-4">
        <Col xs="8">
          <Label for="personName">Your name:</Label>
          <Input
            className="gray-input"
            type="text"
            name="name"
            id="personName"
            placeholder="Enter your name here..."
            innerRef={register}
          />
        </Col>
        <Col xs="2" className="text-center">
          <Label for="personAge">Age:</Label>
          <Input
            className="gray-input"
            type="number"
            name="age"
            id="personAge"
            placeholder="Enter age..."
            innerRef={register}
          />
        </Col>
        <Col xs="2" className="text-center">
          <Label for="personGender">Gender:</Label>
          <Input
            type="select"
            name="gender"
            id="personGender"
            innerRef={register}
          >
            <option defaultValue disabled>
              -
            </option>
            <option>F</option>
            <option>M</option>
          </Input>
        </Col>
      </Row>
      <Row>
        <Col xs="12">
          <Input
            hidden
            id="personLatLon"
            name="personLatLon"
            type="text"
            defaultValue={lonlat}
            innerRef={register}
          />
          <MapContainer onMarkerChange={onMarkerChange} />
        </Col>
      </Row>
      <h4>Inventory</h4>
      <Inventory
        onSubmitInventory={onSubmitInventory}
        currentInventory={inventory}
      />
      <Button
        outline
        color="secondary"
        className="mt-2"
        type="submit"
        form="registerForm"
      >
        Submit
      </Button>
    </Form>
  );
}

export default Register;

EDIT: Updated code with tips from answers, still facing the same problem:(

The only problem I see with your code is that you are trying to pass a second argument to setInventory . I don't believe this works with hooks like it did with class components. When I attempt to type that in typescript it throws an instant error.

Just pass the actual data you are trying to send and then call onSubmitInventory(inventory) for example:

  const onChangeWeapon = (e) => {
    const newInventory = {...inventory, weapon: e.target.value};
    setInventory(newInventory);
    onSubmitInventory(newInventory);
  };

If you have to wait for the next render to call onSubmitInventory , it should be a useEffect in the parent. My next question would be why the parent needs the state and the child also has it as a state? Possibly it should be lifted up. But I'm not sure of that without seeing the parent.


Edit: Is your problem just with the console.log ? If you run

const [state, setState] = useState(true);
// ...
setState(false);
console.log(state); // outputs true.

The value of state does not change until the next render. Calling setState will cause a new render, but until that happens, the old value is there. Add a console.log of state in the parent just in the body like here

const [inventory, setInventory] = useState({
    water: 0,
    soup: 0,
    meds: 0,
    weapon: 0,
  });
console.log('I just rendered, inventory: ', inventory);

You'll see it updates!

The fact that the value of state does not change can bite you in this case:

const [state, setState] = useState(true); // closes over this state!
// ...
setState(!state); // closes over the state value above
setState(!state); // closes over the same state value above
// state will be false on next render

So I use this form if there is any possibility I call setState twice before a render:

const [state, setState] = useState(true);
// ...
setState(state => !state);
setState(state => !state);
// state will be true on next render

Inside your parent component's onSubmitInventory you have named the argument inventory but that already exists in the parent's scope. I suggest renaming that newInventory to be clear about which you are referencing.

Also, it seems like you are keeping track of inventory in both the parent and child's state. Keep it in the parent and pass it down to the child as a prop/props.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM