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/
Related
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.
I recently use react hook well. however sometime I wonder my coding style is okay.
let mDown = false;
let timerId = null;
function App() {
useEffect(() => {
mDown = true
....
}, [])
}
Like bottom code, I declare variable out of function. and I want to use common variable and access, change this value without using state.
Is it anti-pattern?
Thanks to read. :)
yes, of course. you can use the common variable in not only the hook function, but also any other methods of the App.
however the best practice is to make the global variables in other js files like constant.js, and import them in the above of the file.
import { mDown, timerId } from '../constant'
So let's think in terms of components. I mean, let's ignore for a while there barely will be more than one <App /> but think about component-centric approach in general.
Do you mean some data independent for each component's instance? Then you use useState(if changing this data should re-render component) or useRef(if updating this should never trigger re-rendering).
Or should that data be shared among all the components? Then, again it depends. Sometimes global variable is fine(real global variable, as a property on window object), sometimes better to put it into Context API or any state management layer(Mobx, Redux) instead.
Why don't have module-level variable as you aer going to? I see multiple reasons(again, maybe not big deal for exact case with <App />):
Module-level variable sticks to module instance. If we you ever end with multiple version of the same component in the same application, they will have own instances of such a variable. Think of <Dropdown /> component and some variable made to collapse all other instances once some is expanded. In case with module-level variable you may end some dropdown closes some instances - but not all - just if you got few versions of the same package used(like coming as 2nd level dependency)
No access, and no isolation - hard to unit-test.
And finally: what could be a profit here?
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.
I see this code pattern a lot in React code:
render() {
const units = this.props.units;
const temperature = this.state.temperature;
return (<p>{temperature} {units}</p>);
}
and some developers I've asked say that its standard practice to pull state/props into local constants before using them in render or other functions - however, I can't find this practice discussed anywhere in the React docs, and the example code in those docs sometimes just access state/prop attributes directly.
I would prefer to use direct access because it makes the code more readable when you can immediately see where attributes are coming from instead of having to hunt down the local constant definitions.
Before I make this decision though, I was wondering if anyone knew why this practice exists and if there are good reasons to use it?
Is there a functional difference between the example above and this?
render() {
return (<p>{this.state.temperature} {this.props.units}</p>);
}
Its a standard practice to pull state/props when there a MANY props/state that are going to be used inside your functions
eg: const { prop1, prop2, prop3, prop4, ... } = this.props
const { state1, state2, state3, ... } = this.state
You can now reference them with the const names instead of
this.props.propName/stateName everywhere.
You shouldn't do that in the example you provided where there is just 1/few props/state
const units = this.props.units; // Not recommended
Bottomline : Just cleaner code. Matter of preference.
The reason why we need to get constants from props is to destructure the props object. Destructuring means your getting a property of the props object. With that you can lessen the code needed instead of using this.props.yourProps when you destructure it. It wil only be yourProps instead.
Also your destructure should be on es6 for es6 standards: Use
const {units} = this.props; //This gets the key units from the props object (es6 Syntax)
Instead of
const units = this.props.units; // This does the same but without using the es6 syntax
If you Structure your code in this manner you can write less code and can maintain it better... When we follow standards like this we won't worry even the organization switches from one developer to another.
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.