Search code examples
reactjsuse-effectuse-statesetstatearray-map

How to use map in React to create independent children elements


I can successfully use map to create multiple elements from inside my array, but the button that I'm creating and the state I'm assigning to each child element seems to be tied back to the first mapping. Code here along with screen shot.

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

const Switch = () => {
    const [status, setStatus] = useState(true);  
    
    const meats = [
        "Chicken", 
        "Ground Beef",
        "Sausage"
    ]

    const meatSwitches = meats.map((meat) =>
    
            <>
            <div id="ingredientContainer" className={status === true ? 'containerYes' : 'containerNo'}>
                
                    <h2 id='ingredientLabel'>{meat}</h2>
                    <div id="underLabel">
                        <h3 id='yes'>Sounds Yummy! </h3>
                        <input
                            className="react-switch-checkbox"
                            id={`react-switch-new`}
                            type="checkbox"
                            onClick={() => setStatus(!status)}
                            
                        />
                        <label
                            className="react-switch-label"
                            htmlFor={`react-switch-new`}
                        >
                            <span className={`react-switch-button`} />
                        </label>
                        <h3 id='no'>No, thanks.</h3>
                    </div>
                </div>
            </>
    );

How can I get each child element to have a functioning button, individual of the first and with independent states (in this case of 'true' and 'false').


Solution

  • There are 2 common approaches:

    1. Create a Meat child component with its own single boolean state and keep this state at the child level, independent of the parent component Switch. You can pass the status data back to parent if you want, via a callback function onStatusChange, for example with an useEffect().

      const Meat = ({ meat, onStatusChange } ) => {
        const [status, setStatus] = useState(true);
        
        useEffect(() => onStatusChange(status), [status])
        
        return (
        <div
          id="ingredientContainer"
          className={status === true ? 'containerYes' : 'containerNo'}
        >
          // ... your jsx code here
        </div>
      }
    

    And your Switch component will look like this:

    const Switch = () => {
        const meats = [
            "Chicken", 
            "Ground Beef",
            "Sausage"
        ]
    
        const [statusList, setStatusList] = useState([]);  
        const handleStatusChange = (status) => {
          // do what you want with the individual status,
          // perhaps push it in a status array?
          setStatusList((prev) => [...prev, status]);
        };
    
        const meatSwitches = meats.map((meat) => 
          <Meat key={meat} meat={meat} onStatusChange={handleStatusChange} />
        )
    
    

    2. Use an array of meat types instead of a boolean state to store checkbox values, then convert it to boolean values later when needed. With this approach you don't have to create a new Meat child component.

    const Switch = () => {
        const [status, setStatus] = useState([]);  
        
        const meats = [
            "Chicken", 
            "Ground Beef",
            "Sausage"
        ];
        
        const handleStatusChange = (event, meat) => {
          if (event.target.checked) {
            // push meat type to list
            // when box is checked
            setStatus(prev => [...prev, meat]);
          } else {
            // remove meat type from list
            // when box is unchecked
            setStatus(prev => return prev.filter(type => type !== meat));
          }
        };
    
        const meatSwitches = meats.map((meat) =>
          <div
            {/* id and key need to be unique */}
            id={`ingredientContainer-${meat}`}
            key={meat}
            {/* check if box is checked */}
            className={status.includes(meat) ? 'containerYes' : 'containerNo'}
          >
            // code
            <input
              type="checkbox"
              onClick={(event) => handleStatusChange(event, meat)}
            />
            // ...
          </div>
        )
    
    

    Let me know if this answers your question.