So I am new to working on Electron and React. I wanted to implement more functionality but I have run into an issue where when I pass function c into a new Card element it seems to pass the hook in function c at the time of creation.
Such that: When the + button is clicked 3 times it would render 3 Cards as expected, lets call them 0, 1 and 2. However, when their log button is clicked to console log the cards hook, clicking 0's log button console logs:
{}
clicking 1's log button console logs:
{0: {react element of 0}}
clicking 2's log button console logs:
{0: {react element of 0}, 1: {react element of 1}}
However, clicking the - button which i have rigged to troubleshoot this at the moment in the App.js produces:
{0: {react element of 0}, 1: {react element of 1}, 2: {react element of 2}}
Am I wrong to assume that clicking each log button should have instead console logged what the - button in the App.js console logged. Rather than what the hook looked like at the time of the Card elements creation? How would I go about fixing this issue?
Note: the reason why I use an Object to store the React elements instead of an Array is because I would like to eventually add a remove button to remove individual cards. Is there a better way to do what I am doing?
App.js
import './App.css';
import Cards from './Cards/Cards'
import { useState } from 'react';
let KEY = 0
export default function App() {
const [cards, setCards] = useState({})
const c = () => {
console.log(cards)
}
const make = () =>{
const a = <Cards log={c} anID={KEY}/>
const b = {...cards}
b[KEY] = a
KEY += 1
setCards(b)
}
return (
<div>
<div className="time-container">
{Object.values(cards)}
</div>
<button className="add-button" onClick={() => make()} >+</button>
<button className="" onClick={() => console.log(cards)} >-</button>
</div>
);
Card.js
import React from 'react'
import './Cards.css'
export default function Cards({ log, anID }) {
return <div className="card-container">
<div>
<button className="card-buttons-red" onClick={()=> log(anID)}>Log</button>
<button className="card-buttons">Edit</button>
</div>
</div>
}
Once you created <Cards log={c} anID={KEY} /> jsx element using make() function, it won't be changed again. Function signature of c won't be changed, based on the change of cards state when you insert into a particular <Cards/> component as a prop. That simply means, function parameters or its output won't be changed once you created the <Cards/> component.
That's why with 1st Log button it only logs {} and 2nd Log button logs {0: {react element of 0}}.
Here you can observe that the function signature of c function you passed into a child component hasn't been changed once after it's created.
To explain it simply.
Before, executing make() function for the first time, cards object is {}.
Then you can understand when you execute make() function first, passed c function will be as follows.
const c = () => {
console.log({})
}
This won't be changed based on the change of cards.
When you execute make() for the 2nd time, passed c function will be as follows.
const c = () => {
console.log({0: {react element of 0}})
}
I don't think you really need to log cards object inside <Cards/> component. Just log it inside the parent component whenever cards state changed using a useEffect as follows.
let KEY = 0;
export default function App() {
const [cards, setCards] = useState({});
const c = () => {
console.log(cards);
};
const make = () => {
const a = <Cards log={c} anID={KEY} />;
const b = { ...cards };
b[KEY] = a;
KEY += 1;
setCards(b);
};
useEffect(() => {
console.log("cards: ", cards);
}, [cards]);
return (
<div>
<div className="time-container">{Object.values(cards)}</div>
<button className="add-button" onClick={() => make()}>
+
</button>
<button className="" onClick={() => console.log(cards)}>
-
</button>
</div>
);
}
The way React works is that it re-runs your Component function whenever it re-renders (or even more frequently).
Then anything in the Component function body is recreated, to the exception of hooks which may retain some previous information.
In your case, that means your c function and your cards state variable are different references each time.
Then as implied by @KavinduVIndika's answer, you essentially embed these references when creating your child Cards components in your make function and storing them in your cards state. On next re-render, new references are created, leading to your symptom.
An immediate workaround could be to try referencing something that "persists" through rerenders but without being embedded. That is the use case for useRef hook.
function App() {
const [cards, setCards] = useState({})
const c = useRef()
c.current = () => {
console.log(cards)
}
const make = () =>{
const a = <Cards log={c} anID={KEY}/>
const b = {...cards}
b[KEY] = a
KEY += 1
setCards(b)
}
return <>(template)</>
}
// in Cards, call c.current
<button className="card-buttons-red" onClick={()=> log.current(anID)}>Log</button>
But as implied by @KavinduVIndika's comment, there is just a more common React way to avoid such issue completely. It seems to me that it may be due to your attempt of premature optimization, to anticipate your timer usage, but without knowing that React already covers for the most penalizing performance bottleneck, i.e. browser repaint, thanks to its Virtual DOM. Yes, your child Cards component may be re-run (but retain its states with proper usage of ref special prop), but in the vast majority of cases you see little performance impact.
function App() {
const [cards, setCards] = useState({})
const c = () => {
console.log(cards)
}
const make = () =>{
const a = KEY // use plain data, do not store JSX
const b = {...cards}
b[KEY] = a
KEY += 1
setCards(b)
}
return (<>
{Object.values(cards).map(cardData =>
// by building the Cards within the App at render time,
// it now uses the most up-to-date reference of your c function,
// hence avoids past reference issue.
// Performance-wise, React detects that the actual DOM
// does not need visual update, hence no repaint.
<Cards log={c} anID={cardData} ref={cardData} />
)}
</>)
}