Search code examples
javascriptreactjsreact-hooksnext.jsjavascript-objects

Logic of updating an object in react with useState


I have a component renderRoyaltyAccount, that gets rendered x number of times depending on the input that sets royaltyAccount.

In this component I have 2 fields, one for the name of the account, and the second a percentage.

What I wanted to do is depending of the number of accounts to create, create an object with those two fields for each, example :

If he chooses to create two accounts , to have a the end (what I thought but could be not the best choice :) ) :

{
    1: {
      "account": "test1",
      "percentage": 2,
    },
    2: {
      "account": "test@",
      "percentage": 0.5
    }
}

I tried with a useState and updating it with onChange with inputs, but it was a mess LOL.

If anyone could help me with this state, and specially the logic with objects and hooks. Thank you

export default function FormApp() {

    const [royaltyAccount, setRoyaltyAccount] = useState(1);

    const [allAccounts, setAllAccounts] = useState ({
            
                {
                    "account": "",
                    "percentage": 1,
                },
            
        })

    const renderRoyaltyAccounts = () => {
        let items = [];
        for (let i = 0; i < royaltyAccount; i++) {

            items.push(
                <div key={i}>
                    <div>
                        <label>Royalty Account n° {i + 1}</label>
                        <input onChange={()=> setAllAccounts(???)} type="text"/>
                    </div>
                    <div>
                        <label>Royalty %</label>
                        <input onChange={()=> setAllAccounts(???)} type="text"/>
                    </div>
                </div>
            )
        }
        return items;
    }


return (

  <>
    <label> Royalty account(s)</label>
    <input onChange={(e) => { setRoyaltyAccount(e.target.value)}} type="number"/>
    {
      renderRoyaltyAccounts()
     }

  </>

)


}


Solution

    1. Dynamically compute the allAccounts state array from the initial royaltyAccount state value. Add an id property to act as a GUID for each account object.
    2. Create a handleRoyaltyAccountChange onChange handler to either append a computed diff of the current allAccounts array length to the new count value, or to slice up to the new count if less.
    3. Create a handleAccountUpdate onChange handler to shallow copy the allAccounts state array and update the specifically matching account object by id.
    4. Give the inputs a name attributeand pass the mappedallAccountselement object's property as thevalue` prop.

    Code:

    import { useState } from "react";
    import { nanoid } from "nanoid";
    
    function FormApp() {
      const [royaltyAccount, setRoyaltyAccount] = useState(1);
    
      const [allAccounts, setAllAccounts] = useState(
        Array.from({ length: royaltyAccount }).map(() => ({
          id: nanoid(),
          account: "",
          percentage: 1
        }))
      );
    
      const handleRoyaltyAccountChange = (e) => {
        const { value } = e.target;
        const newCount = Number(value);
        setRoyaltyAccount(newCount);
        setAllAccounts((accounts) => {
          if (newCount > accounts.length) {
            return accounts.concat(
              ...Array.from({ length: newCount - accounts.length }).map(() => ({
                id: nanoid(),
                account: "",
                percentage: 1
              }))
            );
          } else {
            return accounts.slice(0, newCount);
          }
        });
      };
    
      const handleAccountUpdate = (id) => (e) => {
        const { name, value } = e.target;
        setAllAccounts((accounts) =>
          accounts.map((account) =>
            account.id === id
              ? {
                  ...account,
                  [name]: value
                }
              : account
          )
        );
      };
    
      return (
        <>
          <label> Royalty account(s)</label>
          <input
            type="number"
            onChange={handleRoyaltyAccountChange}
            value={royaltyAccount}
          />
          <hr />
          {allAccounts.map((account, i) => (
            <div key={account.id}>
              <div>
                <div>Account: {account.id}</div>
                <label>
                  Royalty Account n° {i + 1}
                  <input
                    type="text"
                    name="account"
                    onChange={handleAccountUpdate(account.id)}
                    value={account.account}
                  />
                </label>
              </div>
              <div>
                <label>
                  Royalty %
                  <input
                    type="text"
                    name="percentage"
                    onChange={handleAccountUpdate(account.id)}
                    value={account.percentage}
                  />
                </label>
              </div>
            </div>
          ))}
        </>
      );
    }
    

    Edit logic-of-updating-an-object-in-react-with-usestate