Home > Enterprise >  React Forms - Changing multiple states with multiple event listeners
React Forms - Changing multiple states with multiple event listeners

Time:10-01

I am trying to create a web app that generates a resume as the user types information.

I am having trouble changing, via input/handleChange, the the properties of the objects in my component's state. I know I can change the state object properties by using the input's name as a reference to the property I am trying to change, but I do not know how to do this when there are multiple objects in the component's state.

I have a button that adds an object to the schoolData state and adds the component to the schoolArray state.

Input component:

import React from 'react';
import Schools from './Schools';

export default function Input(props) {  
  // school data and event handler
  const [schoolData, setSchoolData] = React.useState(
    [{
      schoolName: '', schoolState: '', schoolCity: '', schoolDegree: '', schoolStartDate: '', schoolEndDate: '', schoolCurrent: false,
    }]
  );

  function handleSchoolChange(event) {
    const { name, value, type, checked } = event.target;
    setSchoolData(prevData => ({
      ...prevData,
      [name]: type === "checkbox" ? checked : value
    }));
    console.log('school data', schoolData);
    console.log('school data name', schoolData.name);
    console.log('school array', schoolArray);
  }
  
  // school array and event handler
  const [schoolArray, setSchoolArray] = React.useState([
    <Schools key={schoolData.length} schoolData={schoolData} handleSchoolChange={handleSchoolChange} />,
  ]);

  function addSchool() {
    // add schoolData object
    setSchoolData(prevData => [...prevData, {
      schoolName: '', schoolState: '', schoolCity: '', schoolDegree: '', schoolStartDate: '', schoolEndDate: '', schoolCurrent: false,
    }]);
    
    setSchoolArray(prevSchoolArray => [...prevSchoolArray, <Schools key={schoolData.length   1} schoolData={schoolData[0]} handleSchoolChange={handleSchoolChange}  />]);
    console.log(schoolArray);
    console.log(schoolElements);
    console.log('school name', schoolData[0].schoolName);
  }

  const schoolElements = schoolArray.map((school, index) => 
    { return school }
  );

  return (
    <form>

      <legend>Education</legend>
      {schoolElements}

    </form>
  )
}

Schools component:

export default function Schools(props) {
  return (
    <div>
      <label htmlFor="schoolName">School Name</label>
      <input
        type="text"
        placeholder="School Name"
        className="input"
        name="schoolName"
        onChange={props.handleSchoolChange}
        value={props.schoolData.schoolName}
      />

      <label htmlFor="schoolCity">City</label>
      <input 
        type="text"
        placeholder="City"
        className="input"
        name="schoolCity"
        onChange={props.handleSchoolChange}
        value={props.schoolData.schoolCity}
      />

      <label htmlFor="schoolState">State</label>
      <input
        type="text"
        placeholder="State"
        className="input"
        name="schoolState"
        onChange={props.handleSchoolChange}
        value={props.schoolData.schoolState}
      />

      <label htmlFor="schoolDegree">Degree</label>
      <input
        type="text"
        placeholder="Degree"
        className="input"
        name="schoolDegree"
        onChange={props.handleSchoolChange}
        value={props.schoolData.schoolDegree}
      />

      <label htmlFor="schoolStartDate">Start Date</label>
      <input 
        type="month"
        placeholder="Start Date"
        className="input"
        name="schoolStartDate"
        onChange={props.handleSchoolChange}
        value={props.schoolData.schoolStartDate}
      />

      <label htmlFor="schoolEndDate">End Date</label>
      <input 
        type="month"
        placeholder="End Date"
        className="input"
        name="schoolEndDate"
        onChange={props.handleSchoolChange}
        value={props.schoolData.schoolEndDate}
      />

      <label htmlFor="schoolCurrent">Still attending</label>
      <input 
        type="checkbox"
        name="schoolCurrent"
        onChange={props.handleSchoolChange}
        checked={props.schoolData.schoolCurrent}
      />
    </div>
  );
}

Thanks in advance for any help!

CodePudding user response:

The easier solution would be to keep your schoolData state variable updated through the input onchanges. This should keep your schoolData in your Input component up to date to what you want.

Schools component

export default function Schools(props) {
  return (
    <div>
      <label htmlFor="schoolName">School Name</label>
      <input
        type="text"
        placeholder="School Name"
        className="input"
        name="schoolName"
        onChange={(e) => props.setSchoolData({ ...props.schoolData, schoolName: e.target.value })}
        value={props.schoolData.schoolName}
      />

      <label htmlFor="schoolCity">City</label>
      <input 
        type="text"
        placeholder="City"
        className="input"
        name="schoolCity"
         onChange={(e) => props.setSchoolData({ ...props.schoolData, schoolCity: e.target.value })}
        value={props.schoolData.schoolCity}
      />

      <label htmlFor="schoolState">State</label>
      <input
        type="text"
        placeholder="State"
        className="input"
        name="schoolState"
         onChange={(e) => props.setSchoolData({ ...props.schoolData, schoolState: e.target.value })}
        value={props.schoolData.schoolState}
      />

      <label htmlFor="schoolDegree">Degree</label>
      <input
        type="text"
        placeholder="Degree"
        className="input"
        name="schoolDegree"
         onChange={(e) => props.setSchoolData({ ...props.schoolData, schoolDegree: e.target.value })}
        value={props.schoolData.schoolDegree}
      />

      <label htmlFor="schoolStartDate">Start Date</label>
      <input 
        type="month"
        placeholder="Start Date"
        className="input"
        name="schoolStartDate"
         onChange={(e) => props.setSchoolData({ ...props.schoolData, schoolStartDate: e.target.value })}
        value={props.schoolData.schoolStartDate}
      />

      <label htmlFor="schoolEndDate">End Date</label>
      <input 
        type="month"
        placeholder="End Date"
        className="input"
        name="schoolEndDate"
         onChange={(e) => props.setSchoolData({ ...props.schoolData, schoolEndDate: e.target.value })}
        value={props.schoolData.schoolEndDate}
      />

      <label htmlFor="schoolCurrent">Still attending</label>
      <input 
        type="checkbox"
        name="schoolCurrent"
         onChange={(e) => props.setSchoolData({ ...props.schoolData, schoolCurrent: e.target.value })}
        checked={props.schoolData.schoolCurrent}
      />
    </div>
  );
}

CodePudding user response:

Theres quite a lot wrong here. You shouldnt store components in state, you only need the data. This is greatly confusing matters.

What you need to do instead is iterate over schoolData in the render method, and pass down appropriate bindings for the data itself for the individual school, and a callback that handles changes for that school.

import React from 'react';
import Schools from './Schools';

export default function Input(props) {  
  // school data and event handler
  const [schoolData, setSchoolData] = React.useState(
    [{
      schoolName: '', schoolState: '', schoolCity: '', schoolDegree: '', schoolStartDate: '', schoolEndDate: '', schoolCurrent: false,
    }]
  );

  function handleSchoolChange(event, indexToChange) {
    const { name, value, type, checked } = event.target;
    setSchoolData(prevData => {
        return prevData.map((school, index) => index === indexToChange ? {
           ...prevData[index],
           [name]: type === "checkbox" ? checked : value
        }) : school
    });
  }
  


  function addSchool() {
    // add schoolData object
    setSchoolData(prevData => [...prevData, {
      schoolName: '', schoolState: '', schoolCity: '', schoolDegree: '', schoolStartDate: '', schoolEndDate: '', schoolCurrent: false,
    }]);
  
  }

  return (
    <form>

      <legend>Education</legend>
      {schoolData.map((school, index) => (<School key={index} schoolData={school} handleSchoolChange={(e) => handleSchoolChange(e, index)} />)}
    </form>
  )
}

CodePudding user response:



One thing I can suggest is in your Input component, have a single object variable and a object array variable, fill information using the single object, when info is filled add it to the object array, and then pass the object array as a prop into School. Then in the School component you can return the array mapped or something else if array is empty.

function Input(props) {
const [schoolData, setSchoolData] = useState({ single object })
const [schoolsArray, setSchoolsArray] = useState([{},...])

// use setSchoolData to fill out schoolData Variable

const handleAddingSchool = () => {
    setSchoolsArray([...schoolsArray, schoolData])
}


return (
<>
    <School schoolArray={schoolsArray}/>
<>
)
}

// New Basic School Component Layout
function School (props) {
    return {
    <>
        {
            props.schoolArray.length > 0 
            ? <SchoolComponentStuff....>
            : <NothingToSeeHere....>
        }
    <>
    }
}

You will have to fill in your key pieces but I think this will resolve your issue.

  • Related