I have a parent and child components. Whenever the Link in the child component is clicked, I want to execute the parent's click handler that is passed in as props. In the click handler, I simply want to increment an integer state in the parent component.
The issue: the numCars
state is correctly incremented for each single Link (child component) I click. For instance, if I click on the Link "car1" 5 times, the numCars
state is incremented to 5. However, if I then click on "car2", the numCars
state is reset back to 0. If I continue clicking "car2", numCars
is incremented properly, until I click on another car. It's as if each child has its own parent component.
Why is this happening?
export default function Garage() {
const [numCars, setNumCars] = useState(0);
const carHandleClick = (carName) => {
console.log("Clicked by " + carName);
setNumCars(numCars + 1);
}
const cars = [
{
name: "car1"
},
{
name: "car2"
},
{
name: "car3"
}
]
return (
<ul>
{cars.map((car) =>
<Car name={car.name} carHandleClick={carHandleClick}/>
}
<ul>
);
}
export default function Car( {name, carHandleClick} ) {
return (
<li>
<Link to=... onClick={() => carHandleClick(name)}
{name}
</Link>
</li>
)
}
carHandleClick = (carName) => {
setNumCars(prev => prev + 1);
}
you want always to use the functional form of the state setter if you want to update it based on its previous value
or a useCallback hook with [numCars]
:
carHandleClick = useCallback((carName) => {
setNumCars(numCars + 1);
},[numCars])
Update:
the issue was related to the fact navigate with the link unmounts and remounts the parent component so its state is reset.
one way to handle this is to use a context in the top level:
export const MyContext = createContext();
const App = () => {
const [numCars, setNumCars] = useState(0);
const carHandleClick = (carName) => {
console.log("Clicked by " + carName);
setNumCars((prev) => prev + 1);
};
return (
<MyContext.Provider value={{ numCars, carHandleClick }}>
//...
</MyContext.Provider>
);
}
now you juste consume it from both parent and child:
import { MyContext } from "./App";
export default function Garage() {
const { carHandleClick, numCars } = useContext(MyContext);
//...
return (
<ul>
{cars.map((car) => (
<Car name={car.name} />
))}
</ul>
);
}
import { MyContext } from "./App";
function Car({ name }) {
const { carHandleClick, numCars } = useContext(MyContext);
//...
return (
<li>
<Link onClick={() => carHandleClick(name)}>{name}</Link>
</li>
);
}
Learn more about useContext