Immutability is an implementation detail in React? - reactjs

I recently watched a talk by David Nolen where he says that 'immutability is an implementation detail in React'?
What does this mean and if this wasn't the case, how would React be different?

What does "implementation detail" mean:
I would summarize as:
Immutability is a detail of react that you have to implement yourself.
BTW: "Detail" is this case can still mean a lot of work.
React depends on props and state to be immutable.
React does not make props or state immutable for you. You have to ensure that in your code yourself.
So the following code is a recipe for disaster:
// DO NOT TRY THIS AT HOME
var customerObject = { name: "Bill" };
this.setState( customer: customerObject }; // valid react code, triggering re-render
...
customerObject.name = "Karl";
// state still has the same customerObject,
// but the contents of the object have changed. This is where things break down.
React has to ensure that its internal virtual DOM, and all props and states, are always in sync with the actual DOM.
So every time something changes anywhere in a prop or state, react needs to run its render cycle.
How would react be different without immutability:
Without immutability your react implementation may not work properly.
If react were not designed for immutability, then it would not be react (i.e. a state machine) but a different beast altogether.

Immutable Data Structure with ReactJS
The first of all, react team strongly recommend applying immutable data structure like Immutability Helpers or immutable.js. Why? Because we can use "shallow comparison" to increase component re-render performance. like
MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
render() {
...
}
}
According to immutability, the data alway return a new reference if it has been changed. We can easy use shallowEqual(only check reference whether is same or not) to determine component will re-render. If we dont use immutable data, we have to check props or state object deeply not reference to make sure re-rendering.

As for my understanding, each component in React has its own standalone scope and they don't share the variables.
That means when you pass an mutable variable(such as Object or Array) through props to a specific react component. It will clone each variable so that this component will have a totally new environment.
For example, assuming you have component A, and it works like this,
var ComponentA = React.createClass({
render: function() {
var user = { name: 'Tyler', role: 'Developer' };
return (
<SubComponent user={user} />
);
}
});
What ComponentA wants is simply render the user. So it require another module, let's say SubComponent to do that.
var SubComponent = React.createClass({
render: function() {
return (
<div>
<span>Name: {this.props.user.name}</span>
<span>Role: {this.props.user.role}</span>
</div>
);
}
});
For now, we should notice the variable user in ComponentA is different with the variable this.props.user in SubComponent. The this.props.user is not a reference. It's cloned from the ComponentA.
So that means, when you try to change the value of this.props.user in SubComponent, it won't destroy the user in ComponentA. Which is what David Nolen said in his tech talk. ("Change something in data without destroy the old one.")
Of course this would sacrifice some extra spaces, but you can get lots of benefits. Such as each of your component would be totally separated. Then all the nightmares cause by Shared Mutable Variables are gone. Shared Mutable Data is the root of evil, it's unpredictable and unreliable.
Imagine the SubComponent and the ComponentA are share the same user and you want to render another module by passing props user. Then you will update your code into this way,
var ComponentA = React.createClass({
render: function() {
var user = { name: 'Tyler', role: 'Developer' };
return (
<div>
<AnotherComponent user={user} />
<SubComponent user={user} />
</div>
);
}
});
Once we change the name of user in SubComponent(maybe by accident), we will have a cascading effect, and we don't know which one change the variable. That's painful coz then we have to check each line of the code in SubComponent and AnotherComponent. You really don't want to do that, right?
So I think that's what he mean. Hope this can solve your problem. : )

Related

ReactJS: How to render a collection of objects

So I'm quite new on web development last couple of days. I come from c++ background and I can't wrap my head through all the principles of reactjs. I have 2 classes. The child class called JobAd should render some information that it got from props.
export default class JobAd extends Component {
constructor(props) {
super(props);
this.state ={
index: props.index,
id: props.jobId,
name: props.name,
description: props.description,
location: props.location,
adress: props.adress,
alreadyApplied: props.alreadyApplied,
open: false,
// toggleJob: props.toggleJob,
};
this.toggleJob = props.toggleJob;
}
render() {
return (
<div className={`${styles.jobAd} d-flex` + "job " + (this.state.open ? 'open': '')} key={this.state.index} onClick={() => this.toggleJob(this.state.index)}>
<div className={`${styles.jobTitle}`}>
{this.state.location} - {this.state.name}
</div>
<div className={`${styles.jobDetails}`}>
<div className={`${styles.jobDescription}`}> {this.state.description}</div>
<div className={`${styles.jobAdress}`}>{this.state.adress}</div>
<ApplyButton jobId= {this.props.id} alreadyApplied = {this.props.alreadyApplied}/>
</div>
</div>
)
}
}
The second class, queries a mongoDB db and creates jobAd objects populating them from the info gotten from db.
class JobExplorer extends React.Component
{
...
result.data.jobs.forEach(job => {
var find = job.employees.find(obj => obj === userId);
if (!(find === undefined)) {
alreadyApplied = true;
}
var toPush = new JobAd ({
index: i,
id:job._id,
description:job.description,
name:job.name,
location:job.locationName,
adress:job.locationAdress,
alreadyApplied:alreadyApplied,
open:false,
toggleJob: this.toggleJob.bind(this)
});
jobList2.push(toPush);
console.log("look");
console.log(jobList2)
});
this.setState({
jobList: jobList2
})
this.setState({
error: null,
jobs: result.data.jobs
});
...
render()
{
console.log("look2");
console.log(this.state.jobList);
return (
<div><Navigation />
{this.state.jobList}
</div>
);
}
But I am faced with the following error which I cannot find a fix for.
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state, toggleJob}). If you meant to render a collection of children, use an array instead.
How should I instantiate those objects so I could render them using the "architecture" I wrote. Is there a fundamental flaw that I have in my classes?
The below snippet doesn't work because new will return an object (this) not the react component.
So, instead of
var toPush = new JobAd({
index: i,
id: job._id,
...
});
jobList2.push(toPush);
you can do this
var toPush = <JobAd
index={i}
id={job._id}
...
/>;
The above snippet works because <JobAd ... /> is converted to React.createElement(JobAd, ... ). However, you still shouldn't do it like this. since there are a lot of better ways to do this. one of them is:
save just the data in joblist and then render the data list on JobAd component
like below:-
render(){
return this.state.joblist.map((job, i) => (
<JobAd
key={job._id}
index={i}
...
/>
));
}
The key is a really important thing. Read about it: https://reactjs.org/docs/lists-and-keys.html
Things that could be improved:-
Don't copy props in the state as you are doing in JobAd class instead directly render the props.
Don't call setState twice as in JobExplorer. you could set all the keys in
setState at the same time. since that would render the component twice.
Suggestions:-
You should avoid using var as that might cause some issues here.
since, you are just a starter, try using functional component first. they are
quite easier to grasp
You seem to have a misconception about state/props in React and web development. It's very normal; I learned python and Java first and many tutorials seem to assume that people just know this already.
"State" in generally refers to variables containing/referring to values that can change without a page refresh in your application. If you know a value is not going to change, it does not need to be held in state. Storing it in a normal variable is exactly what you should do.
"Props" is just another word for arguments that are passed to React components. There's more to it in reality, but as a beginner, that's all you need to really know for now.
So in your job add, things like name, address, jobs, description shouldn't go in state because they aren't going to change as a result of user interaction or for any other reason, unless the underlying data they are loaded from changes, but then that wouldn't be handled by React but instead by the API that your app gets data from. They should just be rendered, so refer to them like this.props.address in your render method. The value for open, however, need to be in state, because that definitely can change.
As for the error, it looks like you are not calling JobAd correctly. You need to use the syntax <Job Ad/> rather than new JobAd...that won't work in React.
I would recommend doing a tutorial to get the basics down.

Can I make a certain function globally available inside a React application?

It is a common practice to pass in the form of a prop, from a root component A, to a subcomponent B, a function that will change the state of A. Like so:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'foo'
};
this.handleNameChange = this.handleNameChange.bind(this);
}
render() {
return (<NameChanger name={this.state.name} onNameChange={this.handleNameChange} />)
}
handleNameChange: function(newName) {
this.setState({
name: newName
});
}
}
Now as you can see NameChanger is one level down only so not a big issue there. But what if it had been down 3 or even 4 levels? We would have had to pass it down the chain of components and that bothers me big time. Is there a way to make a function globally available within the app?
I looked at Context (https://reactjs.org/docs/context.html) but I am not sure it is the right design choice for globally available functions. Or is it?
Thanks
In a typical React application, data is passed top-down (parent to
child) via props, but this can be cumbersome for certain types of
props (e.g. locale preference, UI theme) that are required by many
components within an application. Context provides a way to share
values like these between components without having to explicitly pass
a prop through every level of the tree.
https://reactjs.org/docs/context.html
Try using Redux or Mobx(very easy to start with) as state management library to solve this problem.

Are state and props mutable?

Let's say I have a react component - a button - that increments a value when I click it.
For example
var Component = React.createClass({
getInitialState: function() {
return {count: 0}
},
increment: function() {
this.setState({count: this.state.count + 1})
},
render: function() {
return (<button onClick={this.increment}>{this.state.count}</button>);
}
})
React.render(<Component />, document.getElementById('react-container'));
The state is mutable!
I can do a similar thing with props
var Component = React.createClass({
increment: function() {
this.setProps({count: this.props.count + 1})
},
render: function() {
return (<button onClick={this.increment}>{this.props.count}</button>);
}
})
React.render(<Component count={0}/>, document.getElementById('react-container'));
The state is mutable!
Some of the resources I have checkouted out say props are immutable, but then go and do something like setProps. The different resources keep contradicting each other. This is making it difficult for me to grasp the difference between state and props.
Are props mutable? If not why can I change them? It seems like changing props is not good practice, is this true? If yes, why does setProps exist?
this.props and this.state can be be mutated but this is really just a consequence of the mutability of objects in JS.
Your assertion that this.setState mutates the current state is false; a new object representing the state is created, with the changes applied.
To be honest I didn't even know that setProps existed but it sounds like a complete anti-pattern! The whole point of props is that they are passed through to the component, enforcing the unidirectional data flow. If the component can change its own props, this flow is broken.
You should really aim to store as much of your application state as possible at the top level i.e. in a store, rather than using component state. If all the state is in one place, it becomes a lot easier to reason about it.
setProps is a holdover from the early days of React, and it's been deprecated for a long time now. You're correct, changing props from within the component isn't a good practice - think of them as like the arguments to a function. Rather than mutating props, you should either:
Use a callback prop to notify the parent that something has happened - the parent can then mutate the data it owns and pass it back into the child through props.
Make use of component state as you did in your first example.
What both of these solutions have in common is that a single component owns the data, and that's the only component that's allowed to modify it - this is commonly referred to as there being a 'Single Source of Truth'. The great thing about structuring your code this way is that it means that you don't get stuck in tangles of spaghetti code trying to work who's mutating a piece of data.

How to avoid complex initial state declaration in React

If I'm, using complex object Structure in React render, how can I avoid redefining that structure in getInitialState method
var UserGist = React.createClass({
getInitialState: function() {
return {
user:{
section: {
country: 'Iran'
}
},
lastGistUrl: ''
};
},
....
....
render: function() {
return (
<div>
{this.state.user.section.country}'s Amex Initial
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
Now the problem is the actual structure of the object used is pretty huge
user:{
section: {
.....
25 levels of nesting
.....{
country: 'Iran'
}
}
}
and that object is coming from backend , so how can I avoid defining the entire object structure in getInitialState()
First of all, state should be shallow. You shouldn't have deep objects as props or state.
Next, why do you even want to do this? Is it so that you don't get a bunch of "xx is not defined" errors on the initial render? If so, why don't you just declare user: {} in getInitialState, and wrap an if (!_.isEmpty(this.state.user)) condition around your render code?
Or, since the data is coming from the server, maybe it's a good thing to re-declare this structure. You can use getInitialState to create placeholder data until the real object shows up.
Also, be aware that setState only does a shallow replacement. If you change any property or sub-property of the user object, you'll need to clone it, change the property, and then call setState({user: clonedAndUpdatedUser}). Or, just call forceUpdate.
You should really just try to get your props and state to be shallow.
Good luck!

React.js shouldComponentUpdate() and react-router Link

I currently have a doubt about the correct combined implementation of react-router Link navigation and shouldComponentUpdate() on the root application level.
That is, I have a root component called App.jsx which contains a global component with a header, footer, sidebar etc and this same component has an ajax long-poll which retrieves new registrations in the system and updates the state when new users register.
Since I don't want to push a re-render to the component (and therefore all it's children) on ajax responses that don't have updates I decided to make use of the lovely shouldComponentUpdate() method.
So, I came up with something like this - noting that I'm making use of lo-dash:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState);
}
With this the component correctly ignores irrelevant responses about the latest registrations.
Now, the problem appears when I have to make the routing. To clarify before, this is the kind of structure of the render():
Note: the _routerTransitionKey is just a helper I have to not make transitions when I'm navigating internal views state and it's working correctly.
<Grid key='app' id="wrapper" className="no-padding">
<Header user={this.state.user} allRegistrations={this.state.allRegistrations}/>
<section id="page-wrapper">
<NotificationArea key='internalNotification' />
<RouteHandler key={_routerTransitionKey} user={this.state.user} allRegistrations={this.state.allRegistrations}/>
</section>
</Grid>
Because I have the RouteHandler inside this global component, I have the issue that a change in the route is completely ignored by it, since the application state itself didn't change. That causes the component to never trigger the render() on navigation and therefore never update the RouteHandler.
What I needed would be something like:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState) || ROUTE_CHANGED ;
}
My question is: does anybody out there knows of a clever approach to this issue? I'm trying to avoid having to create yet another wrapping component to handle the Routes before they reach this App component I currently have...
So, after the tip from #WayneC, even though the react-router doesn't inject the props directly into the react component props, there's a possible way inspired by that approach.
I achieved what I wanted by doing a slight change using not the this.props, but instead the this.context.router.getCurrentPath()
So now the solution looks like this:
shouldComponentUpdate (/*nextProps*/, nextState) {
return !_.isEqual(this.state,nextState) || this.context.router.getCurrentPath() !== _routerTransitionKey;
}
Just to make it clearer, my _routerTransitionKey gets its value from an imported Util that looks mostly like this:
var Utils = {
Router: {
TransitionKey: {
get: function(){
return Router.HistoryLocation.getCurrentPath();
}
}
}
}
_routerTransitionKey = Utils.Router.TransitionKey.get();
This _routerTransitionKey is scoped in an upper level, and I modify it on every render(), so that I keep track of it for later comparison.
And... that's it.

Resources