Vayun
Vayun

Reputation: 101

How do I re-render a component when an object is changed in React

I'm trying to build a simple expense log app in React and would like to display the latest data every time the expensesLog object is updated. On clicking the button, the clickHandler function is called and the object is updated but nothing seems to change on the UI inspite of me mapping the expenseLog object. Here's my code :

import './App.css';
import { useState } from 'react';

function App() {

  var expenseLog = [
    {
      name: "Expense1",
      amount: 1000
    },
    {
      name: "Expense2",
      amount: 100
    }
  ];

  var [count, setCount] = useState(0);
  var [name, setName] = useState("");

  const inputChangeHandler = (e) => { 
    setName(e.target.value);
  }

  const inputChangeHandler2 = (e) => { 
    setCount(e.target.value);
  }
  
  const clickHandler = () => { 
    expenseLog = [...expenseLog, {name: name, amount: count}];
    document.querySelector('.inputField').value = "";
    document.querySelector('.inputField2').value = 0;
  }

  return (
    <div className="App">
      <input onChange={inputChangeHandler} className='inputField'/>
      <input type={"number"} onChange={inputChangeHandler2} className='inputField2'/>
      <button onClick={clickHandler}></button>
      {expenseLog.map(expense => <p>{expense.name} {expense.amount}</p>)}
    </div>
  );
}

export default App;

Upvotes: 4

Views: 19931

Answers (3)

Andy
Andy

Reputation: 63524

  1. Add the data to a new expenses state.
  2. Remove the count and name states, and add a new one input that deals with both input changes.
  3. Add a data attribute to each input. We can use that to update the input state when it changes.
  4. Remove the native DOM methods. When you click the button reinitialise the input state. The change in state will be reflected in the input values.

const { useState } = React;

function Example({ data }) {

  const inputInit = { name: '', amount: 0 };

  const [expenses, setExpenses] = useState(data);
  const [input, setInput] = useState(inputInit);

  function handleChange(e) {
    const { dataset: { type }, value } = e.target;
    setInput({ ...input, [type]: value });
  }
  
  function handleClick() { 
    const { name, amount } = input;
    setExpenses([ ...expenses, { name, amount } ]);
    setInput(inputInit);
  }

  return (
    <div onChange={handleChange}>
      Name: <input data-type="name" type="text" value={input.text}/>
      Amount: <input data-type="amount" type="number" value={input.number} />
      <button onClick={handleClick}>Add entry</button>
      <div className="list">
        {expenses.map(obj => {
          return <p>{obj.name}: {obj.amount}</p>;
        })}
      </div>
    </div>
  );

}

const data=[{name:'Expense1',amount:13},{name:'Expense2',amount:100}];

ReactDOM.render(
  <Example data={data} />,
  document.getElementById('react')
);
* { font-size: 0.95em; }
input { display: block; }
button { margin-top: 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Upvotes: 0

Satel
Satel

Reputation: 187

It's not updating UI as expenseLog is just object, and it's immutable when you don't change the reference.

So, you need to use expenseLog as State variable. And update it inside callback function using setExpenseLog for updating the state.

import "./App.css";
import { useState } from "react";

function App() {
  const [expenseLog, setExpenseLog] = useState([
    {
      name: "Expense1",
      amount: 1000,
    },
    {
      name: "Expense2",
      amount: 100,
    },
  ];)

  var [count, setCount] = useState(0);
  var [name, setName] = useState("");

  const inputChangeHandler = (e) => {
    setName(e.target.value);
  };

  const inputChangeHandler2 = (e) => {
    setCount(e.target.value);
  };

  const clickHandler = () => {
    setExpenseLog(prev => [...prev,{ name: name, amount: count } ])
    
    document.querySelector(".inputField").value = "";
    document.querySelector(".inputField2").value = 0;
  };

  return (
    <div className="App">
      <input onChange={inputChangeHandler} className="inputField" />
      <input
        type={"number"}
        onChange={inputChangeHandler2}
        className="inputField2"
      />
      <button onClick={clickHandler}></button>
      {expenseLog.map((expense) => (
        <p>
          {expense.name} {expense.amount}
        </p>
      ))}
    </div>
  );
}

export default App;

Upvotes: 2

dominickque
dominickque

Reputation: 74

Your variable expenseLog is defined in the function body of your react component. That means react will initialize the variable on every render and thus forget all state that you mutated.

As you did with count and name, you need to wrap expensesLog into a React useState hook in order to maintain state inside your component.

Upvotes: 2

Related Questions