React: use or not to use Immutable.js - javascript

I've looked for Immutable.js motivation when using React.js. As I understand React.js should guarantee immutability of properties. But, I can change view properties in this demo:
var Hello = React.createClass({
render: function() {
var item = { prop: 128 };
return <Test item={item} />;
}
});
var Test = React.createClass({
render: function() {
this.props.item = -2; // Don't throw any error
return <div>{this.props.item}</div>; // Returns -2 instead of object
}
});
React.render(<Hello name="World" />, document.getElementById('container'));
Update: thanks to #FelixKling, now I know that properties will be immutable since React v0.14 (which is comming soon).
Question: What's the motivation to use Immutable.js with React v0.14?
Update2: Can we really change parent's properties? in SubComponent
let somePropVal = { a: 1 };
<SubComponent someProp={somePropVal} />
//....Then in SubComponent
this.props.someProp.a = 2; // somePropVal.a still will be `1` at parent element

After facing all rakes with react pure rendering I want to say that Immutable.js is not about providing deepEqual method. The main point is to clone state on each modification.
Why you need to clone state on each modification?
Everything goes well till view's state consists of primitive values only. But one moment that may happen it be an array or complex object tree.
Suppose you have a view YourView which takes an array from a store YourStore. and you don't want to use Immutable.js, just include Node.LS's deepEqual. For instance:
PureRenderMixin.js
const deepEqual = require('deep-equal');
module.exports = function pureRenderMixin(Component) {
Component.prototype.shouldComponentUpdate = function(nextProps, nextState) {
return !deepEqual(this.props, nextProps) || !deepEqual(this.state, nextState);
};
return Component;
};
YourView.react.js
class YourView extends React.Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
}
componentWillMount() {
YourStore.addChangeListener(this._onChange);
}
_onChange() {
this.setState({array: YourStore.getArray()});
}
}
module.exports = PureRenderMixin(YourView);
YourStore.js
......
getArray() { return _array; }
switch(action.type) {
case ActionTypes.UPDATE_USER_FLAG:
_array[action.index].value= action.flag; // BUG!!!
YourStore.emitChange();
break;
}
Problem #1: shouldComponentUpdate return false instead of true you expect that _array[action.index].value = action.flag; will update YourView, but it doesn't. It doesn't because shouldComponentUpdate will return false.
The reason is that array is just a reference, and at the moment of this.setState({array: YourStore.getArray()}) this.state.array (previous state) is also updated. That means that inside shouldComponentUpdate method this.state & nextState refs will point to the same object.
Problem 1# solution:
You need to copy array reference before update it (i.e. with help of lodash):
YourStore.js
......
getArray() { return _array; }
switch(action.type) {
case ActionTypes.UPDATE_USER_FLAG:
_array = _.cloneDeep(_array); // FIXED, now the previous state will differ from the new one
_array[action.index].value= action.flag; // BUG!!!
YourStore.emitChange();
break;
}
Problem #2: sometimes you need to clone _array multiple times, code get buggy
Suppose you need to update multiple values of _array inside if statements:
case ActionTypes.UPDATE_USER_FLAG:
// You can't clone the _array once, because you don't know which conditions will be executed
// Also conditions may not be executed at all, so you can't clone the _array outside of if statements
if (someCondition) {
_array = _.cloneDeep(_array);
_array[someIndex].value= action.flag;
}
if (anotherCondition) {
_array = _.cloneDeep(_array);
_array[anotherIndex].value= action.flag;
}
YourStore.emitChange();
break;
Problem #2 solution: use Immutable.js. Benefits:
It has clear interface and makes it clear for your colleges that the state should be cloned each time
It has batch updates, so you shouldn't worry about cloning array multiple times

immutable.js boost react perfomance with PureRenderMixin
Source of PureRenderMixin:
var ReactComponentWithPureRenderMixin = {
shouldComponentUpdate: function(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
};
With immutable.js compare two objects in shallowEqual are very fast

Related

ReactJS/Redux - Pure vs Impure Javascript functions?

I have gone through the definitions of the Pure and Impure Javascript functions in the ReactJs Official Docs.
Pure functions are ones that do not attempt to change their inputs, and always return the same result for the same inputs.
Example
function sum(a, b) {
return a + b;
}
Impure function is one that changes its own input.
Example
function withdraw(account, amount) {
account.total -= amount;
}
Now, can somebody tell me, how can I mistakenly make functions impure in React/Redux, where pure functions are required?
React and Redux both need pure functions coupled with immutability to run in a predictable fashion.
If you don't follow these two things, your app will have bugs, the most common being React/Redux not able to track changes and unable to re-render when your state/prop changes.
In terms of React, consider the following example:
let state = {
add: 0,
}
function render() {
//...
}
//pure function
function effects(state,action) {
//following immutability while updating state, not directly mutating the state.
if(action == 'addTen') {
return {...state, add: state.add + 10}
}
return state;
}
function shouldUpdate(s) {
if(s === state){
return false
}
return true
}
state = effects(state, 'addTen')if(shouldUpdate(state)) {
render();
}
The state is held by the state object which has only added property. This app renders the app property. It shouldn't always render the state when anything happens but should check whether a change occurred in the state object.
Like so, we have an effects function, a pure function which we use to affect our state. You see that it returns a new state when the state is to be changed and returns the same state when no modification is required.
We also have a shouldUpdate function which checks using the === operator whether the old state and the new state is the same.
To make mistakes in terms of React, you can actually do the following :
function effects(state,action) {
doRandom(); // effects should only be called for updating state.
// Doing any other stuff here would make effects impure.
if(action == 'addTen') {
return {...state, add: state.add + 10}
}
return state;
}
You can also make mistakes by setting the state directly and not using effects function.
function doMistake(newValue) {
this.state = newValue
}
The above should not be done and only effects function should be used to update the state.
In terms of React, we call effects as setState.
For Redux:
Redux's combineReducers utility checks for reference changes.
React-Redux's connect method generates components that check reference changes for both the root state and the return values from mapState functions to see if the wrapped component actually needs to re-render.
Time-travel debugging requires that reducer be pure functions with no side effects so that you can correctly jump between different states.
You can easily violate the above three by using impure functions as reducers.
Following is taken directly from redux docs:
It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue).
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.
Simply said the state cannot be mutated. A new instance of the state should be returned every time there is a change so
This code is not correct :
const initialStates = {
items: ['item1']
}
export const ItemMaster = (state = initialStates, action) => {
switch (action.type) {
case TYPES.ADD_ITEM:
{
state.items.push(action.item)
return state
}
default:
return state
}
}
This code when written as a pure function below, This returns a new instance of the array it does not modify the actual array itself. This is the reason you should use a library like immer to handle immutability
const initialStates = {
items: ['item1']
}
export const ItemMaster = (state = initialStates, action) => {
switch (action.type) {
case TYPES.ADD_ITEM:
{
state = {...state,items:state.items.concat(action.item)}
return state
}
default:
return state
}
}
You could make pure functions impure by adding API calls or writing codes that result in side effects.
Pure functions should always be on point and self-explanatory, and should not require you to refer 3 or 4 other functions to understand what's going on.
// Pure Function
function USDtoEUR(USD, todayRate) {
return USD * todayRate;
}
// Impure Function
function USDtoEUR(USD) {
const todayRate = getTodayRate();
return USD * todayRate;
}
In case of React / Redux
const mapState = async state => {
const { data } = await whatDoINeed()
let mappedState = {}
if (data.needDolphin) {
mappedState.dolphin = state.dolphin
}
if (data.needShark) {
mappedState.shark= state.shark
}
return mappedState;
}
// Or for Redux Reducer
// Bad
{
setData: (state, payload) => {
const set = whatToSet()
return {
...state,
set.dolphin ? ...{ dolphin: payload.dolphin } : ...{},
set.shark ? ...{ shark : payload.shark } : ...{},
}
}
}
// Good
{
setData: (state, payload) => {
return {
...state,
// Just send only the things need
// to be sent
...payload
}
}
}
This should not be done. Everything a connect function or reducer function needs must be supplied through argument or written within its function. It should never get from outside.

React Component not updating after calling setState in SocketIO event listener

I have a class component. I've added this line in the constructor:
this.handleChangedCells = this.handleChangedCells.bind(this);
I register a SocketIO listener in componentDidMount and call a function like so
socket.on("changedCells", this.handleChangedCells);
I am receiving the data as I console.log it once I receive it. The problem is that the function calls this.setState and it does not update the component. I have
handleChangedCells(data) {
console.log(data);
let new_board = this.state.board;
data.map(arr => {
arr.map(obj => {
new_board[obj.ind[0]][obj.ind[1]] = {
color: obj.color,
alive: obj.alive
};
});
});
this.setState({ board: new_board });
}
The component that I have is as follows:
render() {
return (
<div className="boardContainer" style={container_style}>
{this.state.board.map((row, r_ind) => {
return (
<div className="row" key={r_ind}>
{row.map((col, c_ind) => {
return (
<Cell
key={[r_ind, c_ind]}
cell_row_index={c_ind}
cell_col_index={r_ind}
socketProps={socket}
colorProps={this.state.color}
clickedColorProps={col.color}
aliveProps={col.alive}
/>
);
})}
</div>
);
})}
</div>
);
}
As you can see, the component depends on the state which I update however, the component is not updated... Any idea of what is happening here?
EDIT: Here's a link to my project https://github.com/Dobermensch/ExpressTest.git The file in question is /client/components/Game.js
Edit 2: board is an array of arrays containing objects {color: [x,y,z], alive: boolean} and I am getting the changed cells, (dead cells) and marking them as dead in the board so ideally I would go the the dead cell's index and mark it as dead by board[row][col] = {color: [x,y,z], alive: false}
Edit 3: data structure of board is [[{}.{},{}],[{},{},{}]] and I am changing the properties of objects within the array
I had faced similar issue while I had the combination of nested objects and array. I think setState doesn't handle nested updates very well.
Another problem is in your code. You are actually mutating the state.
let new_board = this.state.board; Here the board is an array. Javascript does not deep clone array and object. You are assigning the refererence of the board array to the new variable.
So, what you can do is just deep clone the board array.
let new_board = this.state.board.map(item => {...item}); // You may need to handle the multiple levels.
Also JSON.stringy and JSON.parse should work.
let new_board = JSON.parse(JSON.stringify(array));
Once, you have the deep clone of the state. You can modify the data and setState should trigger the rerender.
Update 1:
As you are using the state of the Cell to update it. The prop values are not updated on re-render.
So you should use getDerivedStateFromProps in your Cell component to make the prop data available to state.
static getDerivedStateFromProps(props, state) {
return {
r: props.clickedColorProps[0],
g: props.clickedColorProps[1],
b: props.clickedColorProps[2],
alive: props.aliveProps
};
}
Note that the getDerivedStateFromProps will execute on every rerender of the component, even when the state change. So, you should have a condition inside the method to return the proper data.
Please don't do a deep copy with JSON.parse, it is a really bad idea because you're creating new objects for everything and not only the things that change. If you know what pure components are then you know why JSON.parse is bad.
You can change only the things that need to change with the following code:
handleChangedCells(data) {
console.log(data);
let new_board = [...this.state.board];
data.forEach(arr => {
arr.forEach(obj => {
//shallow copy only if it's not copied already
if (
new_board[obj.ind[0]] ===
this.state.board[obj.ind[0]]
) {
new_board[obj.ind[0]] = [
...new_board[obj.ind[0]],
];
}
new_board[obj.ind[0]][obj.ind[1]] = {
color: obj.color,
alive: obj.alive,
};
});
});
this.setState({ board: new_board });
}
If it still "doesn't work" then I suggest creating a sandbox demonstrating the problem. Instead of a real xhr you can just return changed sample data.
Demo of it working is here

React: Array not rendering in correct order after first time

The first time my array is rendered it is in the correct order, however, if it is changed the rendered order remains the same.
For example:
construct() {
this.state = {
test_array: [1,2,3,4]
}
let self = this;
setTimeout(function(){
self.scramble();
}, 5000);
}
scramble() {
this.state.test_array = [3,1,2,4];
this.setState(self.state);
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
Results in:
On the console (the current order, correct):
3
1
2
4
Rendered as DOM (the original order, incorrect):
1
2
3
4
Any idea why this is failing to render in the correct order?
You were very close. Here's a few things I changed to fix it:
construct should be constructor
You always need to call super() as the first line of a constructor. (You don't really need to worry about this, it's an Object Oriented thing; google it if you're curious)
Use "arrow functions" instead of "keyword functions" or .bind(this) to prevent this from changing contexts
Do not modify this.state; always call this.setState if you want it to change
class OrderThing extends React.Component {
constructor() {
super()
this.state = {
test_array: [1,2,3,4]
}
setTimeout(() => {
this.scramble();
}, 5000);
}
scramble() {
this.setState({
test_array: [3,1,2,4]
});
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
}
const div = document.createElement('div')
document.body.appendChild(div)
ReactDOM.render(<OrderThing />, div)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
A few suggestions here.
First of all, there is no construct() in js, but there is constructor().
Secondly, you should always call super method with props as an argument in constructor, like this:
constructor(props) {
super(props);
...
}
Finally, react developers highly recommend to modify state only using setState() method.
So you should rewrite your scramble method.
scramble() {
this.setState({test_array: [3,1,2,4]});
}
This changes should help you a little bit.

When and what part of the state is supposed to be deep cloned in Redux reducer?

I have been writing my reducers like this for a long time:
const init = {
a: 'b'
}
const reducerName = function (state = init, action) {
let newState = _.cloneDeep(state) // using lodash
case 'ACTION_NAME':
newState.a = 'c'
return newState
default:
return state
}
One day I realised, that this is probably a very stupid way of doing it, since I am creating a new object in every reducer every time when action is triggered, even if the state would not change at all.
So, my colleague passed me tweet of Dan Abramov, where he says there is no need to deep clone the state. And this got me thinking, when and how to actually do the deep cloning.
Let's say I have this kind of state in reducer:
const init = {
very: {
deeply: {
nested: 'string'
}
}
notSoDeeplyNested: 'string'
}
So which one of the following would be the right way/closest to the right way to do manage the state:
1)
const reducerName = function (state = init, action) {
case 'ACTION_NAME':
let newState = Object.assign({}, state) // Make a shallow copy
newState.very.deeply = action.deeply
return newState
default:
return state
}
2)
const reducerName = function (state = init, action) {
case 'ACTION_NAME':
let newVery = _.cloneDeep(state.very)
let newState = Object.assign({}, state, very)
newState.very.deeply = action.deeply
return newState
default:
return state
}
3)
const reducerName = function (state = init, action) {
case 'ACTION_NAME':
let newDeeply = _.cloneDeep(state.very.deeply)
let newState = Object.assign({}, state, { very: { deeply: newDeeply } ) // Cloning only the nested part, which actually changes?
newState.very.deeply = action.deeply
return newState
default:
return state
}
The last one doesn't seem proper even for me, but I have hard time wrapping my head around this.
So, as far as I understand at least a shallow copy has to be done everytime, but when am I supposed to deep clone something and which part of the object it would be? And by which part I mean would it be the first level object or only the nested part?
To answer your question: No, you don't deep-clone. Instead, you selectively shallow-copy parts that need changing. You can use destructuring to only target paths with changes while keeping existing references to unchanged parts.
In the following example, it shallow-copies init to a object but replaces very with a new object. The same goes for the very object. It shallow-copies everything from init.very but replaces deeply with the new value.
case 'ACTION_NAME':
return {
...init,
very: {
...init.very,
deeply: action.deeply
}
};
It translates to this in ES5:
case 'ACTION_NAME':
return Object.assign({}, init,
{very: Object.assign({}, init.very,
{deeply: action.deeply})});
In the end, you have a new state object that is comprised partly of existing values and references, and partly of changed values, similar to that of persistent data structures. We changed the objects leading to the changed data, but not everything to the point that it can be called a deep clone.
It can get verbose for deep structures, but Redux advocates shallow objects instead of deeply nested ones.

ReactJS bind a component method the correct way

I am trying to use .bind() when using a method in my component.
The reason is simple: In a loop I am returing Components and extend them with a property which is calling a method. But for every loop-item this I want to extend the this Object with some information (like a key).
Example:
Items.jsx
Items = React.createClass({
eventMethod() {
console.log('this event was triggered by key:', this.key);
},
items() {
let items = [];
let properties = {};
_.each(this.props.items, (itemData, key)=>{
properties.eventMethodInItem = this.eventMethod.bind(_.extend(this, {
key
}));
let {...props} = properties;
let item = <Item {...props} key={key} />;
items.push(item);
});
return items;
},
render() {
return(<div>{this.items()}</div>);
}
});
Item.jsx
Item = React.createClass(...);
In this case (and its working) when the Item Component is triggering the prop "eventMethodInItem" my method "eventMethod" will be called and this.key has the correct value!
So - whats now the question ? Its working perfect, right ?
Yes.
But ReactJS does not want me to do this. This is what ReactJS is telling me as a console log.
Warning: bind(): You are binding a component method to the component. React does this for you automatically in a high-performance way, so you can safely remove this call. See Items
Maybe you think its a "bad" way to add children to the component like I am doing it but in my special case I need to do this in this way - so I need to bind new information to a method.
I'm not going to pretend that I understand what you are trying to do here, but maybe I can help clear it up anyway.
React takes all of the top level methods found on each component and automagically binds them to the context of the component.
This prevents other methods from overriding the context of this and as a result, if you try to rebind the method, React says "Hey don't bother. I already got it" — which is the warning you are seeing.
Assuming that you really want do this (each time you are mutating the outer properties object by overriding the eventMethodInItem property).
properties.eventMethodInItem = this.eventMethod.bind(_.extend(this, {
key
}));
Then I can't see any reason that the eventMethod has to live on the component, rather than just in the scope of the items function.
items() {
const eventMethod = function() {
console.log('this event was triggered by key:', this.key);
}
// ...
_.each(this.props.items, (itemData, key)=>{
properties.eventMethodInItem = eventMethod.bind(_.extend(this, {
key
}));
// ...
});
},
That way you don't have to fight React to get your program to work.
React is already autobinding this when using React.createClass http://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#under-the-hood-autobinding-and-event-delegation
Change your binding to
properties.eventMethodInItem = this.eventMethod.bind(null,key);
and your eventMethod to
eventMethod(key) {
console.log('this event was triggered by key:', key);
}
I also suggest using _.map instead of _.each
items() {
return _.map(this.props.items, (itemData, key) => {
return <Item
handleEventMethod={this.eventMethod.bind(null,key)}
key={key} />;
});
},
Good pattern
https://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes
Before :
class ExampleComponent extends React.Component {
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
this. _handleFoo = this. _handleFoo.bind(this);
}
// ...
}
After :
class BaseComponent extends React.Component {
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
}
class ExampleComponent extends BaseComponent {
constructor() {
super();
this._bind('_handleClick', '_handleFoo');
}
// ...
}
another good hacks for this topic http://egorsmirnov.me/2015/08/16/react-and-es6-part3.html

Categories