Search code examples
javascriptreactjsreact-hooksreact-props

How to save data from different children to a single array in parent component


Here I have three components. Whenever I click the button in any of the child components (First and Second) the parent (Test) component should display text entered into the child component. The problem is that whenever I click the button of other child component a new array is created with data from that particular component. Here's the code:

Test component:

function Test() {
    const [ChildData , setChildData] = useState('');
    
    console.log(ChildData)

  return (
    <div>
        <h1>Test</h1>
        <div>
            {ChildData? <div>{ChildData.map((data)=>{return <div>{data}</div> })}</div>:null }
        </div>
        <First passChildData={setChildData}/>
        <Second passChildData={setChildData}/>       
    </div>
  )
}

First component:

function First(props) {

  const [Data, setData] = useState('');
  const [Items, setItems] = useState('')

  const handlechange = (e)=>{
    setData(e.target.value)
  }
  const handleClick = ()=>{
    if(Data != ''){
      setItems((prevData)=>{
        const newData = [...prevData,Data];
        props.passChildData(newData)
        return newData
      })
      setData('')
    }else{
      console.log("enter something")
    }
  }

  return (
    <div>
        <h1>First</h1>
        <input type="text" onChange={handlechange} value={Data}/>
        <button onClick={handleClick}>Add</button>
        {Data}
        <div>{Items? <div>{Items.map((item)=>{return <div>{item}</div> })}</div>:null }</div>
    </div>
  )
}

Second component:

function Second(props) {
  
  const [Data, setData] = useState('');
  const [Items, setItems] = useState('')

  const handlechange = (e)=>{
    setData(e.target.value)
  }
  const handleClick = ()=>{
    if(Data != ''){
      setItems((prevData)=>{
        const newData = [...prevData,Data];
        props.passChildData(newData)
        return newData
      })
      setData('')
    }else{
      console.log("enter something")
    }
  }

  return (
    <div>
        <h1>Second</h1>
        <input type="text" onChange={handlechange} value={Data}/>
        <button onClick={handleClick}>Add</button>
        {Data}
        <div>{Items? <div>{Items.map((item)=>{return <div>{item}</div> })}</div>:null }</div>
    </div>
  )
}

What I expect is:

  • When I enter "What" into the First component;
  • Then "is" into the Second component;
  • Then "your" into the First component
  • Then "name?" in Second component

I want "What is your name?" (Doesn't matter if it's in different divs or lines; I want every single item to be displayed in the order it was entered) to be shown inside the parent (Test) component.


Solution

  • First, I would not have two components - First and Second - that are nearly identical except for their headings; I would have a single component with heading as a prop.

    Second, I would use a <form> with an onSubmit handler instead of a <button> with an onClick handler because this will allow the user to add an item by, for example, pressing [Enter] while the input field has focus.

    Third, I would use the useEffect hook to listen for changes in the First component to the Items array and then call the passChildData prop function when there is a change. Note that variables that are to reference arrays should be initialized with empty arrays, for example: const [Items, setItems] = useState([]).

    The final code looks like:

    function Test() {
      const [ChildData, setChildData] = useState([]);
    
      const setItem = (newItem) => {
        setChildData([...ChildData, newItem]);
      }
    
      return (
        <div>
            <h1>Test</h1>
            <div>
              {ChildData.map((data, index) => <div key={index}>{data}</div>)}
            </div>
            <First heading="First" passChildData={setItem}/>
            <First heading="Second" passChildData={setItem}/>
        </div>
      )
    }
    
    function First(props) {
      const [Data, setData] = useState('');
      const [Items, setItems] = useState([])
      
      useEffect(() => {
        props.passChildData(Data);
      }, [Items])
    
      const handlechange = (e)=>{
        setData(e.target.value)
      }
      
      const handleSubmit = (e) => {
        e.preventDefault();
    
        if (Data === '') {
            console.log('enter something');
        } else {
            setItems([...Items, Data]);
        }
      }
    
      return (
        <div>
            <h1>{props.heading}</h1>
            <form onSubmit={handleSubmit}>
              <input type="text" onChange={handlechange} value={Data}/>
              <button type="submit">Add</button>
              {Data}
            </form>
            <div>
              {Items.map((item, index) => <div key={index}>{item}</div>)}
            </div>
        </div>
      )
    }
    

    I have created a fiddle for reference.