I'm trying to understand why this closure doesn't work as expected when used from a React component.
// state.ts
const state = { things: [] };
export function setThings(newThings: number[]) {
state.things = newThings;
}
// App.tsx
import { setThings } from "./state";
function App() {
const setThingsToArray = () => setThings([0, 1, 2]);
return (
<div>
<button onClick={setThingsToArray}>Set things</button>
</div>
);
}
When setThings
is called from non-React code it works as expected. It changes the things
property of state
.
However when I try to call it from a React component, as in App.tsx, it doesn't work at all. Debugging shows that when setThings
is called from inside App
, it thinks that state.things
is an empty array []
, the value it was initialised to, even though state.things
already contains lots of items.
This seems to imply that React is somehow using a copied version of setThings
with a copied version of state
. How is this possible? Shouldn't it work as a simple closure and reference the real state
?
I don't want to use React state management like Hooks or Redux because I want high performance for the changes in the array, as this is powering a canvas app with lots of particles and redraws per second. So I don't want to have to go through the entire React lifecycle if I don't need the React components to display the data.
EDIT: I made a mistake when writing up my component code for this question 🤦♂️. I fixed it now. The problem in my question is still happening.
EDIT 2:
Because @KevinB asked for proof that calling setThings
elsewhere is correctly updating my array: https://youtu.be/Gp77NtqNQzo
Every "planet" in the canvas is represented by one item in the array. Coordinates and acceleration of every planet changes on every frame. When planets collide setThings
is called with the changed array of planets. This needs to happen because multiple planets can "crash" into each other and combine into one, thus resulting in a state.things
array with 2 items removed and 1 new item added.
Clicking the button calls setThings
with an array containing only a single planet object. But as you can see it does nothing.
You are not returning anything from the setThings
in state.ts
Yet you are assigning it to a const setThings
in App.tsx
And on the <button onClick={setThings}>
the setThings
you are referencing is the undefined
return from the call you make.
Try assigning an actual function to the <button>
like this:
// App.tsx
import { setThings } from "./state";
function App() {
const setThingsClickHandler = () => {
setThings([0, 1, 2]);
};
return (
<div>
<button onClick={setThingsClickHandler}>Set things</button>
</div>
);
}
It turns out that because I was importing main.ts
and App.tsx
from my HTML file directly via two separate <script>
tags, Parcel created two entirely separate bundles for them, which means that there was a separate state
object in each bundle.
This is why updating one would not affect the other. 😕
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.