Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

ReactJS Adding JSX component to a list recursively

I made a component that has a checkbox that when checked should trigger the addition of the same exact component to an array.

As shown below:

an example of how I want to be like.

The problem is when I try to add another component by checking the "Another color?" checkbox from the newly added component it doesn’t work.

It only adds one component item to the array but doesn’t add more than that.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

The ColorQuantity component

const ColorQuantity = ({anotherColorCallbk,reverseColorCallbk,anotherSizeCallbk})=>{
    const [prodColor,setProdColor] = useState('');
    const [isAnotherColor,setIsAnotherColor] = useState(false);
    const [prodSize,setProdSize] = useState('none');
    const [isAnotherSize,setIsAnotherSize] = useState(false);
    const [prodQuantity,setProdQuantity] = useState(0);
    const anotherColorHandler = e =>{
        setIsAnotherColor(e.target.checked);
        if(!isAnotherColor){
            anotherColorCallbk();   
        }
        else{
            reverseColorCallbk();
        }
    }
    const anotherSizeHandler = e =>{
        if(prodColor.length > 0){
            setIsAnotherSize(e.target.checked);
            anotherSizeCallbk();
        }
    }
    return (
    <div className="color-quantity flex gap-4">
        <div className="color-input flex flex-col">
            <label className="inpt-label">Product Color</label>
            <input type="text" value={prodColor} onChange={e=>setProdColor(e.target.value)} placeholder="Enter the product color." className="inpt" />  
            <div className='moreColor'>
                <input type="checkbox" checked={isAnotherColor} onChange={anotherColorHandler} className='me-3' />  
                <label>Another color?</label>
            </div>     
        </div>
        <div className="size-input flex flex-col">
            <label className="inpt-label">Product Size</label>
            <div className="select-container relative text-white">
                <select name="sizes" value={prodSize} onChange={e=>setProdSize(e.target.value)} id="prodSizes" className='appearance-none outline-none bg-slate-700 py-2 rounded-lg ps-3 pe-10 font-bold focus:border-0 cursor-pointer'>
                    <option value="none">Select Size</option>
                    <option value="xs">XS</option>
                    <option value="s">S</option>
                    <option value="m">M</option>
                    <option value="l">L</option>
                    <option value="xl">XL</option>
                    <option value="xxl">XXL</option>
                </select>
                <div className="icon-container">
                <FontAwesomeIcon className='absolute right-3 top-3' icon={faAngleDown}></FontAwesomeIcon> 
                </div>
            </div>
            <div className='moreSize'>
                <input type="checkbox" checked={isAnotherSize} onChange={anotherSizeHandler} className='me-3' />  
                <label>Another size?</label>
            </div> 
        </div>
        <div className="quantity-input flex flex-col">
            <label className="inpt-label">Product Quantity</label>
            <input type="number" value={prodQuantity} onChange={e=>setProdQuantity(e.target.value)} placeholder="Enter size quantity." className="inpt" />  
        </div>
    </div>
    );
}

The parent component

export const AddProduct = ()=>{
    const [colorQList, setColorQList] = useState([]);
    const anotherColorCallbkHandler = () =>{
        setColorQList([...colorQList, 
        <ColorQuantity key={colorQList.length}
            anotherColorCallbk={anotherColorCallbkHandler} //this one doesn't work
            reverseColorCallbk={reverseColorCallbkHandler}
            anotherSizeCallbk={anotherSizeCallbkHandler}/>]);
            console.log('checked!!')
    }
    const reverseColorCallbkHandler = ()=>{
        const colorInputList = [...colorQList];
        colorInputList.pop();
        setColorQList(colorInputList);
    }
    const anotherSizeCallbkHandler = () =>{

    }
    return (
        <div>
            <ColorQuantity anotherColorCallbk={anotherColorCallbkHandler} reverseColorCallbk={reverseColorCallbkHandler} anotherSizeCallbk={anotherSizeCallbkHandler}/>
            {colorQList}
        </div>
    );
}

I tried to use the recursive function so that it calls the function which add components into array inside the function itself but it didn’t seem to work.

>Solution :

Your problem is that you’re storing JSX in your state. There are not many scenarios where you need to do that. At the moment, when you add <ColorQuantity ... /> to your state in the anotherColorCallbkHandler function, and pass the prop anotherColorCallbk={anotherColorCallbkHandler} to it, the anotherColorCallbkHandler function that you’re passing is the first function reference that was created on the first render, which only knows about the initial state value. That means that future invocations of this function will result in the initial state being updated even though the state has since changed. The easiest fix would be to change your function to use the state updater function to avoid the "stale state" issue by getting access to the latest state value:

setColorQList(colorQList => [...colorQList, 
  <ColorQuantity key={colorQList.length}
    anotherColorCallbk={anotherColorCallbkHandler}
    reverseColorCallbk={reverseColorCallbkHandler}
    anotherSizeCallbk={anotherSizeCallbkHandler}
  />
]);

With this change, the anotherColorCallbkHandler doesn’t rely on state outside of its scope, and instead gets access to the current state from the state updater function, avoiding the stale state issue. However, I would advise against this solution and instead avoid storing JSX in your state to begin with.

To avoid storing JSX in your state, you update it to hold data that tells your render (returned) JSX how to display your UI. Your JSX can map over your state to render the appropriate components. For example:

const AddProduct = ()=>{
  const [colorQList, setColorQList] = useState(() => [crypto.randomUUID()]); // start off the state with a random id in the array so that the returned JSX can display it
  const anotherColorCallbkHandler = () => {
    const newColorQId = crypto.randomUUID(); // get a random ID for the new question (this is used as the `key` in the returned JSX)
    setColorQList(colorQList => [...colorQList, newColorQId]); // I still suggest using the state setter function here, although it's not required
    console.log('checked!!');
  }
  const reverseColorCallbkHandler = () => {
    // Consider using `.filter()` here to update to remove a particular question by its id. At the moment this just removes the last one, even if it's not the last question checkbox that was unchecked.
    if (colorQList.length > 1) { // only allow removal of the question if there is more than 1 item. 
      const colorInputList = [...colorQList];
      colorInputList.pop();
      setColorQList(colorInputList);
    }
  }

  const anotherSizeCallbkHandler = () => {
    // ... your implementation ...
  }

  return (
    <div>
      {colorQList.map(id => <ColorQuantity 
        key={id}
        anotherColorCallbk={anotherColorCallbkHandler} 
        reverseColorCallbk={reverseColorCallbkHandler}
        anotherSizeCallbk={anotherSizeCallbkHandler}
      />)}
    </div>
  );
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading