Update Events in React : order? - reactjs

Suppose I have something like this where both components A and D listen to changes in a global store :
import React from 'react'
import useStore from 'whatever-global-store-manager'
function A() {
const [store] = useStore()
if(!store.currentUser)
return <h1>You must log in</h1>
else return <B/>
}
function B() {
return <C/>
}
function C() {
return <D/>
}
function D() {
const [store] = useStore()
console.log(store.currentUser) // Can it be falsey ?
return <h1>{store.currentUser.name}</h1>
}
In A, when currentUser is falsey, B is not rendered, thus D is not rendered. But suppose this scenario :
At first, currentUser is defined as an object with a name property, so D renders, listens to changes in the store and renders the name.
Then, somewhere else in the app, currentUser is set to null.
In which order are the "listeners" processed ? Is there any chance that function D is executed with currentUser to null even when begin ultimately removed from the component tree ?
Another way to formulate the question : Should I check against currentUser in component D before accessing its name property ?
I was looking in the doc for a rule like "When two components listen to the same event, the one higher in the hierarchy is rendered first, and if it turns out the second one should be unmounted according the first's output, then the second one is never even called", but couldn't find anything. In practice, I know it works, but I would like to be sure that it's not just luck.

I believe this largely depends on the store observer mechanism, so it's hard to give a conclusive answer without knowing which store you're using. If observers are registered in order, that might affect how you need to deal with it.
If you wanna find out for sure, you could console.log your render methods, or use debugger while changing the value of currentUser.
Analysis of a hypotetical implementation: let's say an observer is registered when the component mounts, and unregistered when it unmounts. In this situation, the component A would trigger first (since it was registered first), and cause D to unmount, unregistering his trigger. In this hypothetical scenario, D wouldn't need to check for null.
Unrequested advice: a good thing for you might be centralizing the "data collection" in one parent component, while the children just receive that as props and render (without observing the store). I've found (both from lore and personal experience) that it simplifies a lot the development process.

Another way to formulate the question :
Should I check against currentUser in component D before accessing its name property ?
Yes, it is definitely a good decision: it is preferable that there is one redundant code line, instead of obtaining an error.
I was looking in the doc for a rule like
"When two components listen to the same event,
the one higher in the hierarchy is rendered first...
I think the opposite. Although I neither could find the specific documentation to explaine it, I remember that Components do not update like a cascade. That is the idea of the component oriented programming: each one is an independent entity.
Note: if I understand your example well, you could test this example by adding a setTimeout that wraps the return of function A, right? So this way you can then set currentUser as null and D wil be still rendered and you can see what happens.

Related

Why is using React Context better than just passing around a props object?

I've been reading about the advantages of using Context in React and I am unconvinced. I'm wondering if there's something I've missed.
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
What's the hassle in creating a props object in the main component and just passing it around among the underlings? Something like:
// do this once at top level (I'm assuming [foo, foo_set] and [bar, bar_set] are state variables):
const props = {foo, foo_set, bar, bar_set, thisAndThat, theOther, whatever, etcAndEtc}
// including one component
<MyComponent1 {...props } />
// including another
<MyComponent2 {...props } />
(Maybe better to use another name than props for this object, as the components can have other properties. Anyway.)
Then in MyComponent1 you can access all the props you want, or not access them. Either:
...
const MyComponent1 = (props) => {
...
// here we can use any props we need, props.bar, props.bar_set, props.theOther for example
const localVar = props.bar * 2;
props.bar_set(localVar);
// this changes the value of bar throughout the app
...
}
the advantage of the above, as I see it, is that you can pass around the props object to other sub-sub-components and not worry about whether you have anything missing.
Or:
...
const MyComponent1 = ({bar, bar_set, theOther }) => {
...
// here we can use bar, bar_set, theOther in the same example
const localVar = bar * 2;
bar_set(localVar);
...
}
The advantage of this option being that the syntax is shorter.
So my point is why not just use the standard JavaScript syntax? Why introduce new concepts when there are plenty to assimilate to do all sorts of other things?
Consider a fairly common case for most applications: You have authentication information (eg, the current user), a routing library (eg, react-router), and a theme object (what colors to use). These are needed in components scattered throughout the app.
You want to render a button somewhere down at the tip of the component tree. It's going to show the user's avatar, so it needs the authentication data. It's going to navigate when clicked, so it needs the navigate function from the routing library. And it needs to style itself according to the theme.
This certainly can be done through props, but in order for the button to get the props, every component in the chain above it must get and forward those props too. This could be many components deep, like page component -> section component -> table -> row -> widget -> button, and most of them don't need that information for themselves, so they're just taking the props in order to forward it along.
And you can easily imagine cases where there are more than 3 pieces of data that are needed across the app.
What's the hassle
Most people find this "prop drilling" to be a hassle, but let's assume you don't. You still have the problem that it has bad performance. If every component must receive the full set of "global" values that the app might need, then any time anything changes, the entire app must rerender. Optimizations like react.memo become effectively useless. You will get much better performance if you only pass the props you need.
Easier to edit code (You don't have to delete for example unused variable)
Better redability (You dont see unnescesary variables, and You see which component is using variables)
Lesser performance waste (preventing from consuming unnescesarry variables)
Suppose You got 10 descendants in - You would have to pass one variable through 10 of components.
What if some could have the same variable name ? You would have to edit Your passed variable for a while, then edit back later.
To sum up:
Using Context more efficient than stuffing everything into a single object variable, because it avoids re-rendering the whole app when anything changes.
People think passing a single variable around is more hassle than introducing specific syntax.
Context also allows you to have different values for the same variable in different parts of the app. This is shown here (the best explanation IMHO) : https://beta.reactjs.org/learn/passing-data-deeply-with-context
The above article also specifies that sometimes passing props is the best solution. It gives a list of use cases for context, and the advantages provided in each case.

RxJS and repeated events

I am new to RxJs in general but am investigating a bug in some React code in which, upon an unrelated action, an old event seems to be emitted and rendered to a display error. Think if you had two buttons that generated two messages somewhere on screen, and clicking one button was showing the message for the other button.
Being new to RxJs I'm not positive where the problem lays. I don't see a single ReplaySubject in the code, only Obserables, Subjects, and BehaviourSubjects. So this is either misuse of an RxJs feature or just some bad logic somewhere.
Anyway I found the code with the related Observable and I'm not quite sure what this person was trying to accomplish here. I have read up on combineLatest, map, and pipe, but this looks like pointless code to me. Could it also be somehow re-emitting old events? I don't see dynamic subscriptions anywhere, especially in this case.
Tldr I don't understand the intent of this code.
export interface IFeedback {
id: number
text: string
}
export interface IFeedbackMessages {
message: IFeedback | undefined
}
feedback$ = new BehaviorSubject<IFeedback | undefined>(undefined)
feedbackNotifs$: Observable<IFeedbackMessages> = combineLatest([
feedback$
]).pipe(
map(([feedback]) => ({
feedback
})
))
I also found this which maybe be an issue. In the React component that displays this message, am I wrong but does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
const FeedbackDisplay: React.FC () => {
const [feedbackNotifications, setFeedbackNotifications] = React.useState<IFeedbackMessages>()
React.useEffect(() =>
{
const sub = notification$.subscribe(setFeedbackNotifications)
return () => sub?.unsubscribe()
}, [notifications$])
}
Could it also be somehow re-emitting old events?
Yes, it probably is. BehaviorSubject has the unique property of immediately emitting the last value pushed to it as soon as you subscribe to it.
It's great when you want to model some persistent state value, and it's not good for events whose actual moment of occurrence is key. It sounds like the feedback messages you're working with fall into the second category, in which case Subject is probably a better choice.
does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
Not exactly. useEffect accepts a callback, and within that callback you can optionally return a "cleanup" function. React will hang onto that function until the effect is triggered again, then it calls it to clean things up (which in this case consists of closing out the subscription) to make room for the next effect.
So in this case, the unsubscribe will only happen when the component is rendered with a new value for notifications$. Also worth pointing out that notifications$ will only change if it's either passed as a prop or created within the component function. If it's defined outside the function (imported from another file for example), you don't need to (and in fact should not) put it into useEffect's dependency array.

ReactJs, strugglig again

There is something I really don't understand in reacts and sometime the behaviors seem more about bug features that what is expected.
I was testing an instantiation by array. What is strange that the first time, it work as expected.
{this.state.pma.map((Item, index) => (
<this.props.typePma
key = {index}
ref = {(child) => { child.display(Item)}}
onDelete = {() => this.onDelete(index)}
/>
))}
But if I'm just updating the data even without changing it, like:
appendNewPma(){
var newPma = this.state.pma.slice();
//newPma.push(this.props.typePma);
this.setState({pma:newPma})
}
I've get exception error on this line:
ref = {(child) => { child.display(Item)}}
It complains that child is None (TypeError: Cannot read property 'display' of null
).
But why?!!! Why the logic change when there is no change: The data are not changed, just redisplayed. Why the ref is sending me null object where it is obvious the ref should always give me the instantiated object.
Petyo has already pointed you in the direction of the docs which explain the situation you have come across, but I would suggest that you change your approach, rather than blaming the library.
The whole point of a ref is so that the parent can have a reference to the child. Ideally, these should be used as little as possible but in some cases it is unavoidable. In your case, instead of saving the ref, it looks like you are using it as a sort of broken componentDidMount.
Why not just change the child component to call its display method when it is mounted and/or updated? Additionally, you should consider whether the "display" behaviour can itself be moved into a separate component.
You are facing the caveat that the React docs explain. Your ref callback gets called twice - with the actual element and with null afterwards.

React component mounting/unmounting with regard to if else statement in render

When exactly is a child component constructed vs rerenderes? I googled that the key property might have something to do, but I can't figure it out. Also, say in the parent render function I have if true return A, else return B. What happen in this situation?
If A and B are different components, React will unmount one and mount the other if something has changed.
But, if they are the same, the reconciliation algorithm might just change any refs, change the props and call update. I agree that the docs are not clear enough there and I'm unsure if and when have any changes been made from version to version.
However, if this is the case (A and B are from the same component) you should not be writing your code like that but rather something like this:
<Component { condition ? ...propsA : ...propsB } />

React: Why are props not frozen?

React suggests not to mutate props. I was operating under the impression that when props are passed in, they would be immutable or frozen.
The code below does not throw any errors and mutates the prop. Is this a bug?
try{
var a = this.props.a;
a.push('one')
a.push('two')
a.push('three')
console.log(a)
}catch(err){
console.log(err)
}
I'm not sure of the exact reasoning behind it or if that's documented somewhere but I could take a guess:
Freezing any assignment to a variable is not easy or even necessarily possible outside of using new-ish or even as of yet unimplemented browser features. Also deep freezing all your props in your app could have performance implications and would certainly need to be removed for prod builds.
Can you freeze assignment of a primitive variable? I don't think so. I think babel does it with const by statically checking code at compile time.
And can you freeze objects without Object.freeze or ES7's proxy? Again I don't think so, the es5-shim readme says:
which you cannot possibly obtain in legacy engines
What if someone does mutate something? These props are shallow copied, meaning you won't affect the parent prop unless you modify a nested object. However even if you did it would't affect the app, as that would get wiped out on the next render (just like your modifications).
If I got something wrong please let me know! This is just my reasoning.
It won't throw any error unless you frozen the object(props) explicitly.
Props are received from parents and immutable as far as the Component receiving them is concerned.
i.e React by default the props are immutable in the sense it is the concern of your component to maintain them as data received from parent and use as it is. Where as we can use state to maintain the local(component level data) that can be changed any time and keeping props contract with the parent.
Seems like this is the same behavior as const has, you can't reassign the value, but you can still push to the array or add keys to an object because those are not protected. Anyway, despite the possibility to do those mutations, you should not do this.
this.props.a = [1,2,3]; won't work
this.props.a.push(123); will work.
this.props.a = {name: 'John Doe'} won't work
this.props.a.name = 'John Doe' will work
If you try to reassign a prop in your component a type error will occur.
Uncaught TypeError: Cannot assign to read only property a ...
This is not true. Props are mutable by default. If you're using ES2015 you can make them read-only within your function be doing the following:
const { a } = this.props; // would create a const variable 'a'.
Same as:
const a = this.props.a;
While you won't be able to reassign 'a' now, you can still mutate it. Meaning, a = 'foo'; will throw an error, but you will still be able to do a.push('foo');.
If you want to use immutable data structures, try working with https://github.com/kolodny/immutability-helper or https://facebook.github.io/immutable-js/

Resources