Currently I’ve got a react component that looks like this:
const GeraCard = (cards, cart = false) => { return cards.map((v, i) => { return ( <div key={i} className={styles.card}> <div onClick={() => urlRender(v.url)} className={styles.cardContent}> <div> <span className={styles.cardTitulo}>{v.Nome}</span> </div> <div> <span className={styles.cardData}>{v.Data}</span> <span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span> </div> {cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null} </div> <span className={styles.trash}> <FontAwesomeIcon icon={faTrash} color={"#3c3c3c77"} onClick={(e) => { e.persist() TrashHandler(v.Nome, e) }} /> </span> </div> ); }); };
Based on the cards array, it renders something like this:
Whenever I click the trash button, I make a request to my backend, edit the list on my database and rerender the component based on the now updated “cards”. The problem is that this takes sometime to happen, so i wanted a way to remove it from the dom instantly while my backend does it’s job.
somehting like
{show ? renderCompoennt : null}
I’ve tried using vanilla javascript to grab the parent from the trash can, which would be the card i want to remove, but the results are unpredictable and it’s quite slow as well.
My latest try was this:
const GeraCard = (cards, cart = false) => { return cards.map((v, i) => { const [show, setShow] = useState(true); return ( <div key={i}> {show ? <div className={styles.card}> <div onClick={() => urlRender(v.url)} className={styles.cardContent}> <div> <span className={styles.cardTitulo}>{v.Nome}</span> </div> <div> <span className={styles.cardData}>{v.Data}</span> <span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span> </div> {cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null} </div> <span className={styles.trash}> <FontAwesomeIcon icon={faTrash} color={"#3c3c3c77"} onClick={(e) => { setShow(false); e.persist() TrashHandler(v.Nome, e) }} /> </span> </div> : null } </div> ); }); };
but react won’t let me do this. Even tho its fast, everytime one item gets deleted, react complains that “less hooks were rendered” and crashes the app.
Advertisement
Answer
The problem is that in the first render you have {cards.length} calls to hook “useState” within GeraCard, but after deletion of one card, you will have {cards.length-1} calls to hook “useState”. As the React docs state:
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.
You should extract the content of map callback into separate a component.
const GeraCards = (cards, cart = false) => { return cards.map((v, i) => <GeraCard card={v} index={i} cart={cart} /> ); }; const GeraCard = ({ card, index, cart }) => { const [show, setShow] = useState(true); const v = card; return ( <div key={index}> {show ? <div className={styles.card}> <div onClick={() => urlRender(v.url)} className={styles.cardContent}> <div> <span className={styles.cardTitulo}>{v.Nome}</span> </div> <div> <span className={styles.cardData}>{v.Data}</span> <span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span> </div> {cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null} </div> <span className={styles.trash}> <FontAwesomeIcon icon={faTrash} color={"#3c3c3c77"} onClick={(e) => { setShow(false); e.persist() TrashHandler(v.Nome, e) }} /> </span> </div> : null } </div> ); }