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.
Related
Here's my react component and my reducer function:
const testReducer = (state) => {
const newState = {...state}
newState.counts[0] += 1
return newState
}
function App() {
const [countState, dispatchCount] = useReducer(testReducer, {counts: [0]})
return (
<div className="App">
<h1>{countState.counts[0]}</h1>
<button onClick={dispatchCount}>up</button>
</div>
);
}
When the button is clicked and the reducer is executed, I expect the count displayed in the H1 to increment by one. This happens when the button is clicked the first time, but every subsequent click increments it by 2.
This happens no matter what the count is initialized to. If the value I'm incrementing is not in an array, it works normally.
Can anyone tell me why this is happening?
Issue
newState.counts[0] = += 1 isn't valid syntax. Assuming you meant newState.counts[0] += 1 then you are mutating the state object.
const testReducer = (state) => {
const newState = {...state}
newState.counts[0] += 1 // <-- mutates newState.counts!!
return newState
}
In all likelihood this mutation is being exposed in your app by being rendered within a React.StrictMode component.
StrictMode - Detecting unexpected side effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer <-- this
Solution
Even though you are shallow copying state you still need to return a new counts array reference.
const testReducer = (state) => {
const newState = {
...state,
counts: [state.counts[0] + 1]
};
return newState;
};
There's a commonly used utility hook "useLatest", which returns a ref containing the latest value of the input. There are 2 common implementations:
const useLatest = <T>(value: T): { readonly current: T } => {
const ref = useRef(value);
ref.current = value;
return ref;
};
From https://github.com/streamich/react-use/blob/master/src/useLatest.ts
const useLatest = <T extends any>(current: T) => {
const storedValue = React.useRef(current)
React.useEffect(() => {
storedValue.current = current
})
return storedValue
}
From https://github.com/jaredLunde/react-hook/blob/master/packages/latest/src/index.tsx
The first version isn't suitable for React 18's concurrent mode, the second version will return the old value if used before useEffect runs (e.g. during render).
Is there a way to implement this that's both concurrent-safe and consistently returns the correct value?
Here's my attempt:
function useLatest<T>(val: T): React.MutableRefObject<T> {
const ref = useRef({
tempVal: val,
committedVal: val,
updateCount: 0,
});
ref.current.tempVal = val;
const startingUpdateCount = ref.current.updateCount;
useLayoutEffect(() => {
ref.current.committedVal = ref.current.tempVal;
ref.current.updateCount++;
});
return {
get current() {
// tempVal is from new render, committedVal is from old render.
return ref.current.updateCount === startingUpdateCount
? ref.current.tempVal
: ref.current.committedVal;
},
set current(newVal: T) {
ref.current.tempVal = newVal;
},
};
}
This hasn't been thoroughly tested, just wrote it while writing this question, but it seems to work most of the time. It should be better than both versions above, but it has 2 issues: it returns a different object every time and it's still possible to be inconsistent in this scenario:
Render 1:
ref1 = useLatest(val1)
Create function1, which references ref1
Commit (useLayoutEffect runs)
Render 2:
useLatest(val2)
Call function1
function1 will use val1, but it should use val2.
Here is what I think is correct:
const useLatest = <T extends any>(current: T) => {
const storedValue = React.useRef(current)
React.useLayoutEffect(() => {
storedValue.current = current
})
return storedValue.current
}
Is there a way to implement this that's both concurrent-safe and consistently returns the correct value?
The question doesn't actually explain what "this" means, i.e. how is useLatest called, and what purpose it fulfills in the application. So I'll have to guess for that ;) A somewhat realistic example would be very helpful.
In any case, it's probably useful to take a step back and ask if useLatest is the most suitable solution. If you find you don't need it, you also won't have to fix it.
With the way it works (depending on an effect to capture the value), it indeed won't play well with concurrent features. But even without them, it's an unreliable approach as the ref theoretically can change at any point, making renders unpredictable.
My guess of the use case is something similar to the proposed (and partially accepted) useEvent hook (GitHub PR).
function Chat() {
const [text, setText] = useState('');
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}
Its purpose is to capture the latest render's scope, like useCallback, but without the need for dependencies. It does this by using an unchanging callback that internally calls the latest created callback, in a ref that is re-assigned on every render. That way passing that callback as a prop won't cause any renders by itself.
You can implement this yourself already, but the RFC mentions some open questions about this approach.
export function useEvent(handler) {
const latestHandlerRef = useRef();
useLayoutEffect(() => {
latestHandlerRef.current = handler;
});
// Never changing callback.
return useCallback((...args) => {
latestHandlerRef.current(...args)
}, []);
}
I also tested setting latestHandlerRef.current = handler directly in render instead of the layout effect. For now this seems to work as expected but that's just my use case. In the PR some doubt is expressed over assigning to a ref during render, though possibly these concerns don't really apply here, as the ref is only ever accessed in the callback.
I am building a React app and I have a code following this logic:
class ComponentTest extends Component {
state = {test: 0}
testingHandler = () => {
console.log(this.state.test)
}
updateHandler = () => {
let test = Math.random()
this.setState({test})
this.testing()
}
render () {
return (
<button onClick={this.updateHandler}>Update</button>
)
}
}
What I need to do is get the updated value of test in testingHandler(), however it doesn't happen, testingHandler just get the past value, it is like the state that is being received by testingHandler is always one step late.
For example, let's say that test = 0, then I click on the button that calls updateHandler() and now test = 1, after that updateHandler() calls testingHandler(), which I expect to get the new value of test which now is 1, however I get the old value 0. If I call updateHandler() again and update test to 2 I'll get 1 in testingHandler(), which is the past value.
How can I get this value updated from testingHandler?
I did everything that I could imagine, but I couldn't get this done. I believe it has something to do with the render logic of React, but I don't know exactly how this is working.
P.S. Passing the value as an argument is not an option.
setState is asynchronous.
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
You can pass a callback function as the second argument of setState that will run right after the state has updated. So in you case, you could do this:
updateHandler = () => {
let test = Math.random()
this.setState( {test}, this.testingHandler )
}
copied from react documentation link: https://reactjs.org/docs/faq-state.html
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState. Pass an updater function instead of an object if you need to compute values based on the current state
better you can pass it as a callback function or you can also pass the value to the function.
This link is also helpful: setState doesn't update the state immediately
class ComponentTest extends Component {
state = { test: 0 };
testingHandler = () => {
console.log(this.state.test);
};
updateHandler = () => {
let test = Math.random();
this.setState({ test }, () => this.testingHandler()); // pass it as a callback
};
render() {
return <button onClick={this.updateHandler}>Update</button>;
}
}
I was using redux to check how it scales with my application. There are few things, that I found as roadblock when I was using it.
There are high possibility that I am not thinking the redux way / not using the way it was supposed to be used or have not read the doc properly.
I have read the basic section this docs.
The problem statement is fairly simple.
I have two property in store
{
x: 10,
y: 20
}
lets say x is the x-position of the point and y is the y-position of the point. There is one condition if the x goes above 50, y value becomes equal to x.
So I have this
let x = (state = 10, action ) => {
if (action.type === 'cx') {
return action.value;
}
else {
return state;
}
}
let y = (state = 20, action ) => {
if (action.type === 'cy') {
return action.value;
}
else {
return state;
}
}
let model = redux.combineReducers({x: x, y: y});
let store = redux.createStore(model);
let actions = {
changeX: (val) => {
if ( val > 50) {
store.dispatch(actions.changeY(val));
}
return {type: 'cx', value: val }
},
changeY: (val) => ({type: 'cy', value: val })
}
console.log('INITIAL STATE', '---', store.getState());
store.subscribe(() => {
console.log('SUB2', '---', store.getState());
// Paint the dom with new state
});
So the moment
store.dispatch(actions.changeX(60));
is called the subscriber's function gets called twice hence the two times dom painting happen.
Is there a redux-way / workaround to solve this?
You are trying to relate to x and y as part of the same sub model equation - when one is updated, the other maybe updated also.
Using combineReducer you can update related state in the same reducer.
According to Redux guide, if you want that states to be separated, sometimes combineReducer is not enough, and you can breach that pattern into more openly reducer.
The combineReducers utility included with Redux is very useful, but is
deliberately limited to handle a single common use case: updating a
state tree that is a plain Javascript object, by delegating the work
of updating each slice of state to a specific slice reducer. It does
not handle other use cases, such as a state tree made up of
Immutable.js Maps, trying to pass other portions of the state tree as
an additional argument to a slice reducer, or performing "ordering" of
slice reducer calls. It also does not care how a given slice reducer
does its work.
The common question, then, is "How can I use combineReducers to handle
these other use cases?". The answer to that is simply: "you don't -
you probably need to use something else". Once you go past the core
use case for combineReducers, it's time to use more "custom" reducer
logic, whether it be specific logic for a one-off use case, or a
reusable function that could be widely shared. Here's some suggestions
for dealing with a couple of these typical use cases, but feel free to
come up with your own approaches.
An example that is given related for this case:
function combinedReducer(state, action) {
switch(action.type) {
case "A_TYPICAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
b : sliceReducerB(state.b, action)
};
}
case "SOME_SPECIAL_ACTION" : {
return {
// specifically pass state.b as an additional argument
a : sliceReducerA(state.a, action, state.b),
b : sliceReducerB(state.b, action)
}
}
case "ANOTHER_SPECIAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b : sliceReducerB(state.b, action, state)
}
}
default: return state;
}
}
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