1

I have a nested object as state like below -

const [userInfo, setUserInfo] = useState({
    author:"",
    user: {
      name: 'rahul',
      email: '[email protected]',
      phone: [{ primary: '8888888810' }, { alternate: '7777777716' }]
    }
  });

I want to have 5 input fields - author, name, email, primary, and alternate and want to use only one handleChange() method to change the fields.

You can find the code I wrote on the link - https://stackblitz.com/edit/react-ngpx7q

Here, I am not able to figure out how to update the state correctly. Any help would be greatly appreciated.

8
  • 1
    Does this answer your question? How to build a nested object on handleChange with React? Commented May 25, 2021 at 16:11
  • Why are you using an array of objects for the phone? Just use an object. Commented May 25, 2021 at 16:12
  • @YaakovAinspan I was given this problem during an interview. Commented May 25, 2021 at 16:18
  • @MattU not exactly :( Commented May 25, 2021 at 16:20
  • 1
    Perhaps part of the interview was to recognize suboptimal state shape? A flat object is straightforward to work with in the UI, you can nest properties or otherwise create a new object shape when submitting to any services. Maybe the question was to identify the nested properties by dot notation, i.e. id="user.phone.primary" and they wanted you to write the logic to split this and recursively access the correct property. Hopefully you asked clarifying questions for what they wanted, sometimes interview questions are purposely ambiguous. Commented May 25, 2021 at 16:21

5 Answers 5

2

Since this was an interview question then I'd avoid 3rd-party libraries. You can use a switch statement to handle the differently nested state, namely the name and email in the second level and primary and alternate in the third level.

const handleChange = (e) => {
  const { name, value } = e.target;

  switch (name) {
    case "name":
    case "email":
      setUserInfo((userInfo) => ({
        user: {
          ...userInfo.user,
          [name]: value
        }
      }));
      break;

    case "primary":
    case "alternate":
      setUserInfo((userInfo) => ({
        user: {
          ...userInfo.user,
          phone: userInfo.user.phone.map((el) =>
            el.hasOwnProperty(name)
              ? {
                  [name]: value
                }
              : el
          )
        }
      }));
      break;

    default:
    // ignore
  }
};

Demo

Edit react-handling-nested-objects-as-state-using-hooks

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the code. But I am not able to change the value of alternate input in your code. I tried to debug the code but not able to find any bugs as well.
@MohitChauhan Oh, I'm terribly sorry, I used secondary instead of alternate for the second phone field name switch case in the handler. I updated my answer and linked codesandbox. Please check again.
1

you can use lodash set to assign the value for the deeply nested object. You need to pass the path to the name prop of your input .

import set from 'lodash/set'

const App = () => {
  const [userInfo, setUserInfo] = useState({
    author:"",
    user: {
      name: 'rahul',
      email: '[email protected]',
      phone: [{ primary: '8888888810' }, { alternate: '7777777716' }]
    }
  });

  const handleChange = (e) => {
    // clone the state
    const userInfoCopy = JSON.parse(JSON.stringify(userInfo));
    set(userInfoCopy, e.target.name, e.target.value)
    setUserInfo(userInfoCopy)
  }

  console.log(userInfo)

  return (
    <div>
      <input
        name="user.name"
        onChange={handleChange}
      />
       <input
        name="user.phone.[0].primary"
        onChange={handleChange}
      />     
    </div>
  );
};

Now you can use a single handleChange method for updating all your keys in the state .

Comments

0

Instead of treating phone as object of array, which i don't think is a good idea, treat it as single object with primary and alternate as key value pairs

import React, { useState } from 'react';
import './style.css';

export default function App() {
  const [userInfo, setUserInfo] = useState({
    user: {
      name: 'ravi',
      email: '[email protected]',
      phone: {
        primary: 345345345345,
        alternate: 234234234234
      }
    }
  });

  const handleChange = e => {
    console.log(e.target.name);
    setUserInfo(prevState => {
      return {
        user: {
          ...prevState.user,
          [e.target.name]: e.target.value,
          phone: {
            ...prevState.user.phone,
            ...{ [e.target.name]: e.target.value }
          }
        }
      };
    });
  };

  const {
    name,
    email,
    phone: { primary, alternate }
  } = userInfo.user;

  console.log(userInfo);

  return (
    <div className="App">
      Name: <input name="name" value={name} onChange={e => handleChange(e)} />
      <br />
      Email:{' '}
      <input name="email" value={email} onChange={e => handleChange(e)} />
      <br />
      Primary:{' '}
      <input name="primary" value={primary} onChange={e => handleChange(e)} />
      <br />
      Alternate:{' '}
      <input
        name="alternate"
        value={alternate}
        onChange={e => handleChange(e)}
      />
      <br />
    </div>
  );
}

1 Comment

Giving error. Phone is not an object but an array
0

This works based on your original data (where phone is an array of objects):

const handleChange = e => {
    let name = e.target.name;
    if (['name', 'email'].includes(name)) {
      setUserInfo(prevState => {
        return {
          user: {
            ...prevState.user,
            [name]: e.target.value,
          }
        };
      });
    } else {
      setUserInfo(prevState => {
        return {
          user: {
            ...prevState.user,
            phone: name === 'primary' ?
             [prevState.user.phone.find(e => Object.keys(e).includes('alternate')), {[name]: e.target.value}] :
             [prevState.user.phone.find(e => Object.keys(e).includes('primary')), {[name]: e.target.value}]
          }
        };
      });
    }
  };

Comments

0

I copy paste your code and only edit your handleChange

import React, { useState } from 'react';
import './style.css';

export default function App() {
  const [userInfo, setUserInfo] = useState({
    user: {
      name: 'ravi',
      email: '[email protected]',
      phone: [{ primary: '9999999990' }, { alternate: '9999998880' }]
    }
  });



  const handleChange = e => {
    console.log(e.target.name);
    let arrPhone = userInfo.user.phone;
    (e.target.name == 'primary' || e.target.name == 'alternate' ) 
    && arrPhone.map(x => (x.hasOwnProperty(e.target.name)) && (x[e.target.name] = e.target.value))

    console.log(arrPhone)
    setUserInfo(prevState => {
      return {
        user: {
          ...prevState.user,
          [e.target.name]: e.target.value,
          phone: arrPhone
        }
      };
    });
  };

  const {
    name,
    email,
    phone: [{ primary }, { alternate }]
  } = userInfo.user;

  console.log(userInfo);

  return (
    <div className="App">
      Name: <input name="name" value={name} onChange={handleChange} />
      <br />
      Email: <input name="email" value={email} onChange={handleChange} />
      <br />
      Primary: <input name="primary" value={primary} onChange={handleChange} />
      <br />
      Alternate:{' '}
      <input name="alternate" value={alternate} onChange={handleChange} />
      <br />
    </div>
  );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.