How to link input type number value with onChange React

Viewed 135

I´m trying to add the value that the user inputs into the number of pages field, everything I´ve tried so far is not working. Here is the raw code without onChange for the inputs fields.

I´ve tried to use onChange without any luck, the value for the fields always remains the starting value, I´ve tried to change it through useState but no luck either.

Could anyone show me how to go about this?

import "./App.css";

function App(props) {
  const [total, setTotal] = useState(0);
  const [cost, setCost] = useState({
    website: false,
    seo: false,
    google: false,
    pages: 1,
    languages: 1,
    total: 0,
  });
  const [isWebChecked, setIsWebChecked] = useState(false);

  useEffect(() => {
    calculateTotal();
    console.log("useEffect up and running");
    console.log(cost.pages);
    setIsWebChecked(cost.website);
  }, [cost]);

  const handleOnChange = (event) => {
    let { name } = event.target;
    let newCost = { ...cost };
    newCost[name] = !newCost[name];

    setCost((prev) => (prev = newCost));
    if (cost.website === true) {
      setIsWebChecked(true);
    }
  };

  const calculateTotal = (event) => {
    let newTotal =
      0 +
      (cost.website && 500) +
      (cost.seo && 300) +
      (cost.google && 200) +
      ((cost.pages > 1 || cost.languages > 1) &&
        cost.pages * cost.languages * 30);
    console.log(cost.pages);
    setTotal((prev) => (prev = newTotal));
  };

  return (
    <div classname="App">
      <h3> Which services do you require?</h3>
      <p>
        <input
          type="checkbox"
          name="website"
          onChange={handleOnChange}
          value="500"
        />{" "}
        A website (500 €)
      </p>
      {isWebChecked && (
        <div className="modal">
          <label htmlFor="pages">Number of web pages </label>
          <input type="text" name="pages"></input>
          <br />
          <br />
          <label htmlFor="lang">Number of languages </label>
          <input type="number" name="languages"></input>
        </div>
      )}
      <p>
        <input
          type="checkbox"
          name="seo"
          onChange={handleOnChange}
          value="300"
        />{" "}
        A SEO consultancy (300 €)
      </p>
      <p>
        <input
          type="checkbox"
          name="google"
          onChange={handleOnChange}
          value="200"
        />{" "}
        A Google Ads Campaign (200 €)
      </p>

      <p>
        {" "}
        <strong>Total price: {total} €</strong>{" "}
      </p>
    </div>
  );
}

export default App;```
2 Answers

There's an example on this page called "Handling Forms": https://www.w3schools.com/react/react_forms.asp You will need to assign the state you are changing in your on change handler (handleOnChange for you if I see it right) to the value attribute of the input you want to change.

So for instance for the Number of web pages field it would look something like: <input type="text" name="pages" onChange={handleNumOfPagesChange} value={numOfPages}> where numOfPages would be your state. Also I would avoid complicated objects as state (looking at cost state).

We have this:

  const [cost, setCost] = useState({
    website: false,
    seo: false,
    google: false,
    pages: 1,
    languages: 1,
    total: 0,
  });

Then we can update the cost.pages & cost.languages state whenever the input value change with this:

  <input
      type="text"
      name="pages"
      onChange={(e) => setCost({ ...cost, pages: e.target.value })}
  />

  <input
      type="number"
      name="languages"
      onChange={(e) => setCost({ ...cost, languages: e.target.value })}
  />

Your calculateTotal have to use react useCallback hooks since the calculation depends on cost state.

  const calculateTotal = useCallback(
    (event) => {
      let newTotal =
        0 +
        (cost.website && 500) +
        (cost.seo && 300) +
        (cost.google && 200) +
        ((cost.pages > 1 || cost.languages > 1) &&
          cost.pages * cost.languages * 30);
      setTotal((prev) => (prev = newTotal));
    },
    [cost]
  );

Create calculation trigger, where calculateTotal will be called on any cost changes:

  useEffect(() => {
    calculateTotal();
  }, [cost, calculateTotal]);

And add useEffect watcher to reset both cost.pages & cost.languages when deselect cost.website:

  useEffect(() => {
    !cost.website &&
      (cost.pages > 1 || cost.languages > 1) &&
      setCost({ ...cost, pages: 1, languages: 1 });
  }, [cost]);

And finaly, the working codes is as follows:

function App() {
    const [total, setTotal] = React.useState(0);
    const [cost, setCost] = React.useState({
        website: false,
        seo: false,
        google: false,
        pages: 1,
        languages: 1,
        total: 0,
    });
    const [isWebChecked, setIsWebChecked] = React.useState(false);

    const calculateTotal = React.useCallback(
        (event) => {
        let newTotal =
            0 +
            (cost.website && 500) +
            (cost.seo && 300) +
            (cost.google && 200) +
            ((cost.pages > 1 || cost.languages > 1) &&
            cost.pages * cost.languages * 30);
        setTotal((prev) => (prev = newTotal));
        },
        [cost]
    );

    React.useEffect(() => {
        calculateTotal();
    }, [cost, calculateTotal]);

    const handleOnChange = React.useCallback(
        (event) => {
        const { name } = event.target;
        const newCost = { ...cost };
        newCost[name] = !newCost[name];
        setCost((prev) => newCost);
        setIsWebChecked(newCost.website);
        },
        [cost]
    );

    React.useEffect(() => {
        !cost.website &&
        (cost.pages > 1 || cost.languages > 1) &&
        setCost({ ...cost, pages: 1, languages: 1 });
    }, [cost]);

    return (
        <div className="App">
            <h3> Which services do you require?</h3>
            <p>
                <input
                type="checkbox"
                name="website"
                onChange={handleOnChange}
                value="500"
                />{" "}
                A website (500 €)
            </p>
            {isWebChecked && (
                <div className="modal">
                <label htmlFor="pages">Number of web pages </label>
                <input
                    type="text"
                    name="pages"
                    onChange={(e) => setCost({ ...cost, pages: e.target.value })}
                />
                <br />
                <br />
                <label htmlFor="lang">Number of languages </label>
                <input
                    type="number"
                    name="languages"
                    onChange={(e) => setCost({ ...cost, languages: e.target.value })}
                />
                </div>
            )}
            <p>
                <input
                type="checkbox"
                name="seo"
                onChange={handleOnChange}
                value="300"
                />{" "}
                A SEO consultancy (300 €)
            </p>
            <p>
                <input
                type="checkbox"
                name="google"
                onChange={handleOnChange}
                value="200"
                />{" "}
                A Google Ads Campaign (200 €)
            </p>

            <p>
                {" "}
                <strong>Total price: {total} €</strong>{" "}
            </p>
        </div>
    );
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

Related