From what I've understood from Dan Abramov's egghead video 'javascript-redux-colocating-selectors-with-reducers' and some of his tweets, it is a good practice to use a selector to map the state to a prop and remove this logic from the Component and placing it in the Reducer (where the state is managed).
Although this makes all the sense, it also causes my component to render everytime a new state is added to the store, even when only a non related property of the state object was changed. Is there a way to overcome this without using reselectors, which might be a bit overkill for the simpler cases?
As you may know, mapStateToProps is called every time your store is updated.
Whether the component will re-render depends on what mapStateToProps returns. (Actually, it depends on the combined props object returned by mapStateToProps and mapDispatchToProps.)
React Redux (the library that provides the connect function) makes a shallow equality check on the returned object and the last returned object. If the equality check succeeds (i.e. the previously returned object is determined to be equal to the next returned object), the component will not re-render. If the check fails, the component will re-render.
For example, let's say you always return the following object from mapStateToProps:
{
items: [],
}
This object will never be equal to itself ([] === [] returns false because they're different arrays). The equality check will thus fail and the component will re-render.
However, React Redux performs a more complex equality check that that (the implementation of its shallowEqual function can be found here).
For example, even though { a: 'b' } === { a: 'b'} returns false (they're different objects), shallowEqual will pass them off as equal. This is because shallowEqual will compare each key of the returned object with each key of the previously returned object, but only one level deep. More details can be found in the implementation I linked to above.
In summary, if you don't want your component to re-render, you'll need to make sure that the equality check succeeds.
You can:
Save the returned object into the state using a reducer
Cache the result using Reselect
Implement shouldComponentUpdate in the component by hand
These suggestions come straight from Redux's FAQ page: https://redux.js.org/docs/faq/ReactRedux.html#react-rendering-too-often
You can also make sure your mapStateToProps function returns objects that are considered equal by shallowEqual (e.g. objects without arrays, and only one level deep).
For simplicity, I would opt for Reselect.
The short answer is: No it is not.
But there is common mistake that causes unnecessary renders of components when you're using selectors. You should always make sure to define your selector once. So what does it mean?
When you are using connect method, you can pass mapStateToProps method as an argument. Object returned by this method will be passed as props to your component and if you define your selector inside this object it will be redefined each time your component receives a prop. Here is an example for that:
Defining your selector like this could cause your component to render unnecessarily. This is because each time you pass a prop to your component you're basically redefining getSettings method.
#connect(state => ({
getSettings: ()=>'sample output',
}))
class Sample extends React.Component {}
Correct way is the define your selector like this, so that it'll be only created once and the reference passes through your mapStateToProps argument.
const getSettings = () =>'sample output';
#connect(state => ({
getSettings,
}))
class Sample extends React.Component {}
Related
I'm new to react. In the examples I've found so far, using the react context api, you create your own context object and hand it as value to providers, etc. All of the examples seem to store the entire context (either as one object or the sum of its parts) in state and utilize that for requests. But what is the lifetime of the object? Is it only created once during page refresh, or is it recreated during each render cycle such that it can only survive via useState or useRef? In other words, is useState actually needed to store the context data between render cycles, or is useState only being used as a way to subscribe to changes?
This is the way I've been creating context based on the tutorials I've seen...
(To be clear, my question is about the lifetime of the "contextData" object in context.js, and whether rerenders or state changes or some other lifecycle event causes the script including the contextData instantiation and createContext to be rerun, or if some other weird interaction between createContext, contextData, and useContext is going on.)
context.js
import React from "react";
const contextData = {
prop1: "something",
prop2: "somethingElse",
func1: someFunction(stuff) {
prop1 += ".";
},
};
const myContext = React.createContext(contextData);
export {myContext};
function MyContextProvider({children}) {
render <myContext.Provider value={myContext}>{children}</myContext.Provider>;
}
export default MyContextProvider;
index.js
...
root.render(
<MyContextProvider><App /></ContextProvider>
);
SomeComponent.js
import {useContext} from "react";
import {myContext} from "./context"
function SomeComponent() {
const ctx = useContext(myContext);
return <span>{ctx.prop1}</span>
}
This is a bit tautological, but the context object is re-created whenever the function that creates it runs again. The function will (almost always) be either a component or custom hook.
The React philosophy is that the view should flow from the state, so state changes result in (functional) components running again to determine how to change the view (if at all). The same is true if state is included in a context object - if a state setter inside the context object is called, the functional component enclosing the initializer of that state runs again, resulting in a new object being passed down to consumers.
In other words, is useState actually needed to store the context data between render cycles
Yes.
But what is the lifetime of the object?
One particular context object will generally be garbage collected once there's been a re-render and no children reference the old object in a stale closure.
All context objects will get garbage collected only after the component surrounding the context provider unmounts.
With regards to the code in the question - by mutating an object that gets passed down, you're breaking the standard React workflow, which should avoid mutation for those sorts of values. It would be far better to call .createContext in a parent component and to pass down a state setter that changes the prop1 property.
That said, if you don't do that - the top level of context.js only runs once, so you only create the object once, so that one object will exist forever. (which could be a mistake in case you ever change the code so that you want to use the context somewhere else too - the context value will include the old mutations)
I have a side effect that detects the browser language and dispatches a browserLanguageSupported action if it is a language that my application can handle.
Now I have following reducer function that only updates the states preferredLanguage property in case it is not defined already. This is important because there are other actions that update this state property and I do not want a late browserLanguageSupported action to overwrite such a state update.
export interface State {
preferredLanguage: AppLanguage | undefined;
rehydrationComplete: boolean;
}
export const initialState: State = {
preferredLanguage: undefined,
rehydrationComplete: false
};
export const reducer = createReducer(
initialState,
on(LanguageActions.browserLanguageSupported, (state, {browserLanguage}) => {
if (!state.preferredLanguage) {
return {...state, preferredLanguage: browserLanguage};
}
return state;
})
);
Now for my question: Is it good practice to have such a condition in a reducer operator? The function itself is still pure. But I am not sure if it is good design or if I should solve it differently, lets say by adding state slice selection in the side effect that dispatches this action.
Btw. the reason I am not setting it directly in the initial state is because I get the browser language from an angular service and I am not sure if it is even possible to set initial feature state from service injection?
Best regards,
Pascal
I would to this the same way, so you get a 👍 from me.
Adding a slice of the state into the effect just adds needless complexity.
The reducer contains the state, and it's OK to add logic to see if state needs to be updated or not.
Also, let's say you need to add this logic into another action/effect.
Having it in the reducer makes it easier to reuse if it's needed. Otherwise you end up with duplicate logic.
As long as the rejection (or mutation) of the data is irrelevant to the chain of actions & effects, this is absolutely valid.
However, it's worth noting that if the action in question triggers an effect which triggers an action, the triggered action will not know whether the data was rejected (or mutated) without checking the state—which is exactly what this pattern is attempting to avoid.
So, if you wanted to be able react to that rejection (or mutation), you would want to handle this in the effect. But, if you would proceed in exactly the same manner regardless of the result, then it belongs reducer.
When using a selector, I thought that I could do whatever I wanted with the variable without modifying the state, so I was surprised that the state became mutated.
So if this is wrong (in a redux saga):
const filters = yield select(state => state.filters.filters);
filters.terms['helloo'] = "mutated";
//send data with request
yield put(requestData(filters)
How come that first line is a direct reference to the state?
Anyway, if I try using Object.assign, it also mutates state:
const filters = Object.assign({}, yield select(state => state.filters.filters));
filters.terms['helloo'] = "mutated";
How do I create a selection that is a copy of the state?
There's truly no "magic" involved here. Redux's getState() is literally just return state, and both hand-written selectors and Reselect return whatever you have written the functions to return. So, in that example, filters is the actual object reference that's nested inside the store state, because that's what your function returned.
Per the Redux docs page on "Immutable Update Patterns", you need to copy all levels of nesting that you want to update. In your example, you're making a copy of filters, but not filters.terms, so terms is also still the original object that's in the store. You would need to make a copy of that as well, and modify the copy.
There might be a gap in my understanding of how Reselect works.
If I understand it correctly the code beneath:
const getTemplates = (state) => state.templates.templates;
export const getTemplatesSelector = createSelector(
[getTemplates],
templates => templates
);
could just as well (or better), without loosing anything, be written as:
export const getTemplatesSelector = (state) => state.templates.templates;
The reason for this, if I understand it correctly, is that Reselect checks it's first argument and if it receives the exact same object as before it returns a cached output. It does not check for value equality.
Reselect will only run templates => templates when getTemplates returns a new object, that is when state.templates.templates references a new object.
The input in this case will be exactly the same as the input so no caching functionality is gained by using Reselect.
One can only gain performance from Reselect's caching-functionality when the function (in this case templates => templates) itself returns a new object, for example via .filter or .map or something similar. In this case though the object returned is the same, no changes are made and thus we gain nothing by Reselect's memoization.
Is there anything wrong with what I have written?
I mainly want to make sure that I correctly understand how Reselect works.
-- Edits --
I forgot to mention that what I wrote assumes that the object state.templates.templates immutably that is without mutation.
Yes, in your case reselect won't bring any benefit, since getTemplates is evaluated on each call.
The 2 most important scenarios where reselect shines are:
stabilizing the output of a selector when result function returns a new object (which you mentioned)
improving selector's performance when result function is computationally expensive
I have a normal selector which is just used to get part of the state:
export const getAllPosts = state => {
return state.posts;
};
If I use the reselect to wrap the selector:
import { createSelector } from 'reselect';
export const allPosts = createSelector(
getAllPosts,
(posts) => posts
);
Does it make any sense such as improving performance ? In my opinion, the wrapper is unnecessary.
No, it does not make sense to create a memoized selector just to get part of the state tree.
The reason is that connect will do it’s own shallow equality check for each prop passed in from mapStateToProps. If the prop returned by the selector passes that shallow equality check, along with the other props, then render will not be called unnecessarily. If the selector simply returned a part of the state tree and that part of the state tree had not been modified then a shallow equality check will be sufficient.
However, If the selector is computed from the results of other selectors then using createSelector is a good choice. Firstly, it provides a nice syntax for composing selectors. Secondly, if the computation of combining the selectors is potentially expensive you will get a performance benifit. Thirdly, if the selector was to return a new, but equivelent, object or array then the shallow equality check provided by connect would not be sufficient. In that case the memoization that createSelector provides would ensure that the same object or array instance was returned if the inputs had not changed and then the shallow equality check would be sufficient to avoid costly re-renders.
So for just exposing parts of the state tree createSelector doesn’t add anything.
For nearly all selectors that are computed from multiple parts of the state tree createSelector begins to add value. The amount of value that it adds varies based on the selector from just being easier to read up to ensuring you don’t re render the component tree unnecessarily.
In your case it does not make sense cause you just return the same reference from the store which is always shallow equal to itself (or it's previous state) unless you modify it.
If you imagine a different scenario for example a scenario where you store your entities in an object instead of an array in the store but you want to return an array to your Component then you need to derive data:
export const selectAllPosts = createSelector(
getAllPostsFromStore, // returns { 1: {...}, 2: {...} }
(allPosts) => Object.keys(allPosts).map(key => allPosts[key])
);
Now your selector turned into a performance boost cause it only computes the derived data when something in the store changes.
So my rule of thumb is: If you don't derive data, you don't need to memoize it.
No, that provides no real benefit.
What I've found is that my first-level selectors are simply plain functions, and any selectors that go deeper than that need to be memoized:
// assume state is: {first : {second {} } }
const selectFirst = state => state.first;
const selectSecond = createSelector(
selectFirst,
first => first.second
);