Mohit Chauhan
Mohit Chauhan

Reputation: 123

React - using nested objects as state with hooks to fill form data

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.

Upvotes: 1

Views: 2698

Answers (5)

Drew Reese
Drew Reese

Reputation: 202836

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

Upvotes: 2

wisnuaryadipa
wisnuaryadipa

Reputation: 760

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>
  );
}

Upvotes: 0

lacidexh
lacidexh

Reputation: 106

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}]
          }
        };
      });
    }
  };

Upvotes: 0

Parse Shyam
Parse Shyam

Reputation: 366

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>
  );
}

Upvotes: 0

Shyam
Shyam

Reputation: 5497

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 .

Upvotes: 1

Related Questions