Document that setting a previously undefined property does not trigger a re-render.
Closed this issue · 6 comments
Hi
I just stumbled upon a small "nice-to-know".
If using useGlobal()
in a simple (functional/stateless) component, the global state settings your are accessing must be initalized prior to the call to useGlobal()
. This might be obvious to some, but I was wondering why my component would not re-render itself when updating the global, but as soon as I made sure to initialize the global beforehand, it started working.
Apparently this is not mentioned in the documentation, but if it is, then my apologies.
Good catch. I'll try to get this into the documentation for 2.0.
The issue is that ReactN does not use JavaScript Proxies, due to their poor browser support (10% of browsers do not support them).
Because of this, ReactN cannot know that you attempted to access some undefinedProperty
of the state object (myGlobalState.undefinedProperty
), because there is no JavaScript mechanism by which to know this (other than the aforementioned Proxy
class).
Unfortunately, without Proxy
, JavaScript can only subscribe to attempts to access existing properties of an object.
I may be able to adjust this so that useGlobal('undefinedProperty')
will trigger a re-render by creating that property on request, but there is nothing I can do for useGlobal()[0].undefinedProperty
.
So long as 10% of the Internet would render a Component differently, I think the best implementation is to set the global state with an initial value beforehand, ensuring that 100% of users get the same experience.
Sorry about this!
Thanks the explanation, that makes perfect sense and I agreed with your decision to not use proxies for now.
I wouldn't regard this as any issue or drawback for using ReactN, it's only nice to know beforehand, then it's quite easy to initialise the state :)
As a side note I must compliment your work on ReactN - we are moving from React with Redux to React with hooks and ReactN and so far the feeling is much better and the code much less cluttered :)
I'm happy to hear about your positive experience. 😁
I'm facing this issue (I think) even when globalState
is initialized before hand. At the very root of my app I am doing the following:
setGlobal({
playbackState: TrackPlayer.STATE_NONE,
})
I then have a component:
export const Badge = (props: Props) => {
const [global] = useGlobal<GlobalState>()
return (
<View style={[styles.badge, props.style]}>
<Text style={[styles.badgeText, props.textStyle]}>
{statusText(global.playbackState)}
</Text>
</View>
)
}
however this component does not re-render when the globalState
is updated elsewhere in the app.
It only seems to re-render if I access the global state in a parent component, even just doing console.log(this.global.playbackState)
(inside a class component).
I've found #53 which, I think, explains why this is happening. However, from the docs:
Use getGlobal to return a current snapshot of the global state. You only want to use this in helper libraries, and not in Components. Components should use useGlobal or this.global to ensure that they re-render when the global state changes. getGlobal will not cause a Component reliant on the global state to re-render, nor will it cause a library function to re-execute. It does nothing more than return a current snapshot of the global state.
which suggests that the component should re-render when using useGlobal
, and that we should not use getGlobal
in components.
If you are using setGlobal
before the app mounts, and TrackPlayer.STATE_NONE
is not undefined
, your use case should work and useGlobal
should cause Badge
to re-render when global.playbackState
changes.
If <Badge />
mounts before setGlobal
is called, you may be running into the issue of the OP.
Hm, there is no way <Badge />
is mounted before that call. I had to resort to using the withGlobal
method.