Honestly, after hours of research, I totally don't understand reselect, so I just ask why I need it, and if it can help.
Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
It's a bit unclear, what do we mean as argument here, but I assume that not the redux state, because otherwise there wouldn't be any point in reselect.
My goal is to not calculate the whole list every time something happens, because it can contain thousands of elements.
First question is that if for an example the value of state["2"] changes to 4 for an example, it will run through the whole list?
//for an example, in the next list, the key is the child,
//and the value is it's parent
const state = {
1: 5,
2: 5,
3: 2,
4: 1,
5: 10,
//...
1000: 342
};
//and we have to find all children of a specific element,
//what we get from the component's own props
const getState = (
state,
props //okay, I can access it here
) => state;
const getChildren = createSelector(
[getState],
state => Object.keys(state).filter(child => {
const parent = state[child];
return parent === props.id //but I need it here
})
);
const mapStateToProps = (state, props) = ({ children: getChildren(state, props) });
And the main question: how can I access the props inside the function body?
You can pass the props argument directly to the other selector getChildren and you don't need the first getState like this:
const getChildren = createSelector(
[
state => state,
props => props
], (state, props) => {...}
About clarifying the use cases for reselect:
it does recompute if the state or the props changes (any argument indeed). So why using it? I use it for 2 reasons
you can combine parts of state coming from multiple reducers and build up what we can call a 'meta-reducer' and pass that to your component. In that way you place that code only in one place (the selector) and you can reuse it across different components. Imagine each reducer like a database table and the selector like a query result. You can query anything from your state and you want to keep the result cached for performance.
instead of running this logic on the mapStateToProps which is run every time that a component renders (no matter if the state has changed), you run it only 1 time per state change and you get the cached version if the component rerenders. This happens for example if a child component renders only because its parent rendered but the state portion related to its selector didn't change. So I like to use selectors all the times instead of accessing the redux state directly.
Here's the typical flow.
You'll have some ConnectedComponent that's hooked into connect, and its mapStateToProps calls out to a selector with both state and ownProps.
You have individual selectors for both the getting of id off of props and your objects from state.
Using ConnectedComponent
<span>
<ConnectedComponent id="123" />
</span>
mapStateToProps (ConnectedComponent)
import {connect} from 'react-redux'
import {getMyObjectSelector} from './selectors';
const mapStateToProps = (state, ownProps) => ({
myObject: getMyObjectSelector(state, ownProps)
});
export default connect(mapStateToProps)(Component)
selectors
const getIdFromProps = (state, props) => props.id
const getMyObjectsFromState= state => state.myObjects;
export getMyObjectSelector = createSelector(
getMyObjectsFromState,
getIdFromProps,
(objects, id) => objects[id]
);
Component
export const Component = ({myObject}) => (
<span>
// Do stuff
</span>
)
Related
I'm working on some code, and this code is huge. We have so many child components(nearly 300) and each one of them are using & manipulating values from the parent component's state via React Context.
Note: I didn't wrote this code from scratch. Which also means the design I'm about to show is not what I would come up with.
The problem: Since every component is using the same state, there are so many unnecessary re-renders happening. Every small state change is causing every component to re-render. And it makes the web app laggy. Literally, there is lag when you enter some input in a field.
I think, when state change happens the functions get rebuilt and that's why every child gets updated, because the value provided by context is changed after state change happened.
Additionally, I tried to use useReducer instead of useState that didn't went well. Besides that, I tried to use React.memo on every child component but the compare function didn't get triggered no matter what I tried. compare function only got triggered on the parent component which has the state as props. At this point, I'm not even really sure what is the problem :D
To give more specific details on the design, here is how we define and pass the callbacks to child components.
Definitions:
const [formItemState, setFormState] = React.useState<FormItemState>({} as FormItemState);
const getAppState = useCallback(() => ({ state: props.state as AppState }), [props.state]);
const getAppAction = useCallback(() => ({ action: props.action as AppAction }), [props.action]);
const getFormItemError = useCallback((key: string) => formItemErrorState[key], [
formItemErrorState,
]);
const getFormItem = useCallback((key: string) => formItemState[key], [formItemState]);
const updateFormItem = useCallback(
(name: string, formItemData: FormItemData): void => {
const previousState = getFormItem(name);
if (!isEqual(previousState, formItemData)) {
formItemState[name] = formItemData;
setFormState((state) => ({
...state,
...formItemState,
}));
}
},
[formItemState, setFormState]
);
Passing them to Context.Provider:
return (
<FormContext.Provider
value={{
getAppAction,
getAppState,
getFormItem,
updateFormItem
}}
>
<SomeComponent>
{props.children} // This children contains more than 250 nested components, and each one of them are using these provided functions to interact with the state.
</SomeComponent>
</FormContext.Provider>
);
Last note: Please ask me if more info is needed. Thanks!
Why dont you just rewrite your state management to redux and pull only the necessary state to be used on each component. React.memo only picks up changes from props
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
(quote from https://reactjs.org/docs/hooks-reference.html#usereducer)
I'm interested in the bold part, which states that useReducer should be used instead of useState when being used in contexts.
I tried both variants, but they don't appear to differ.
The way I compared both approaches was as follows:
const [state, updateState] = useState();
const [reducerState, dispatch] = useReducer(myReducerFunction);
I passed each of them once to a context object, which was being consumed in a deeper child (I just ran separate tests, replacing the value by the function that I wanted to test).
<ContextObject.Provider value={updateState // dispatch}>
The child contained these functions
const updateFunction = useContext(ContextObject);
useEffect(
() => {
console.log('effect triggered');
console.log(updateFunction);
},
[updateFunction]
);
In both cases, when the parent rerendered (because of another local state change), the effect never ran, indicating that the update function isn't changed between renders.
Am I reading the bold sentence in the quote wrong? Or is there something I'm overlooking?
useReducer also lets you optimize performance for components that
trigger deep updates because you can pass dispatch down instead of
callbacks.
The above statement is not trying to indicate that the setter returned by useState is being created newly on each update or render. What it means is that when you have a complex logic to update state you simply won't use the setter directly to update state, instead you will write a complex function which in turn would call the setter with updated state something like
const handleStateChange = () => {
// lots of logic to derive updated state
updateState(newState);
}
ContextObject.Provider value={{state, handleStateChange}}>
Now in the above case everytime the parent is re-rendered a new instance of handleStateChange is created causing the Context Consumer to also re-render.
A solution to the above case is to use useCallback and memoize the state updater method and use it. However for this you would need to take care of closure issues associated with using the values within the method.
Hence it is recommended to use useReducer which returns a dispatch method that doesn't change between re-renders and you can have the manipulation logic in the reducers.
Practical observation on useReducer and useState -
UseState:
In my React Native project I've 1 screen containing 25+ different states created using useState.
I'm calling an api in useEffect (componentDidMount) and on getting the response based on some conditions, I'm setting up these 25 states, calling 25 state setter function for each function.
I've put a re-rendering counter and checked my screen is re-rendered 14 times.
re-rendering count likewise :
let count = 0;
export default function Home(props) {
count++;
console.log({count});
//...
// Rest of the code
}
UseReducer :
Then I've moved these 25 states in useReducer states, And used only single action to update these states on API response.
I've observed there is only 2 re-render.
//API calling method:
fetchData()
{
const response = await AuthAxios.getHomeData();
dispatch({type: 'SET_HOME_DATA', data: response.data});
}
//useReducer Code:
const initialStaes = {
state1: null,
state2: null,
//.....More States
state27: null,
state28: null
}
const HomeReducer = (state, action) => {
switch (action.type) {
case 'SET_HOME_DATA': {
return {
...state,
state1: (Data based on conditions),
state2: !(some Conditions ),
//....More states
state27: false
}
}
}
}
Advantage of useReducer in this case :
Using useReducer I've reduced number of re-renders on the screen, hence better performance and smoothness of the App.
Number of lines is reduced in my screen itself. It improved code readablity.
When you need to care about it
If you create a callback on render and pass it to a child component, the props of that child will change. However, when the parent renders, a regular component will rerender (to the virtual dom), even props remain the same. The exception is a classComponent that implements shouldComponentUpdate, and compares props (such as a PureComponent).
This is an optimization, and you should only care about it if rerendering the child component requires significant computation (If you render it to the same screen multiple times, or if it will require a deep or significant rerender).
If this is the case, you should make sure:
Your child is a class component that extends PureComponent
Avoid passing a newly created function as a prop. Instead, pass
dispatch, the setter returned from React.useState or a memoized
customized setter.
Using a memoized customized setter
While I would not recommend building a unique memoized setter for a specific component (there are a few things you need to look out for), you could use a general hook that takes care of implementation for you.
Here is an example of a useObjState hook, which provides an easy API, and which will not cause additional rerenders.
const useObjState = initialObj => {
const [obj, setObj] = React.useState(initialObj);
const memoizedSetObj = React.useMemo(() => {
const helper = {};
Object.keys(initialObj).forEach(key => {
helper[key] = newVal =>
setObj(prevObj => ({ ...prevObj, [key]: newVal }));
});
return helper;
}, []);
return [obj, memoizedSetObj];
};
function App() {
const [user, memoizedSetUser] = useObjState({
id: 1,
name: "ed",
age: null,
});
return (
<NameComp
setter={memoizedSetUser.name}
name={user.name}
/>
);
}
const NameComp = ({name, setter}) => (
<div>
<h1>{name}</h1>
<input
value={name}
onChange={e => setter(e.target.value)}
/>
</div>
)
Demo
I cannot get my component to re-render on a state change. I am fairly confident that I am not mutating the state.
component (Domain)
...
<Add
onClick={() => {
domain.routes.push({
from: 'foo',
to: 'bar'
});
this.props.setDomain(this.props.domainIndex, domain);
}} />
...
reducer
case 'SET_DOMAIN':
let tmpState = {...state}
tmpState.domains[action.index] = action.value;
return {...tmpState}
action
export const setDomain = (index, domain) => ({
type: 'SET_DOMAIN',
value: domain,
index: index
});
container
import { connect } from 'react-redux';
import { setDomain, setRoute } from '../actions';
import Domain from '../components/pages/domain';
const mapStateToProps = state => ({
domains: state.domains
});
const mapDispatchToProps = dispatch => ({
setDomain: (index, domain) => dispatch(setDomain(index, domain)),
setRoute: (domainIndex, routeIndex, route) =>
dispatch(setRoute(domainIndex, routeIndex, route))
});
export default connect(mapStateToProps, mapDispatchToProps)(Domain);
From the redux chrome extension I can see that setDomain is being called and the state is being returned correctly:
React Redux tries to improve performance by doing shallow equality
reference checks on incoming props in shouldComponentUpdate, and if
all references are the same, shouldComponentUpdate returns false to
skip actually updating your original component.
It's important to remember that whenever you update a nested value,
you must also return new copies of anything above it in your state
tree. If you have state.a.b.c.d, and you want to make an update to d,
you would also need to return new copies of c, b, a, and state.
From https://redux.js.org/faq/react-redux#react-not-rerendering
{...object} is only a shallow clone. The object's child object will still have the same reference. Therefore, to Redux, the child object is not updated.
I think the quickest way to solve this is to use Lodash cloneDeep.
let tmpState = _.cloneDeep(state);
or
tmpState.domains = _.cloneDeep(tmpState.domains);
But this does come with a performance cost.
Alternatively, you can shallow clone the affected child 1 by 1.
A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.
My app has a state which may or may not have a user object.
I have two containers and two components:
ParentContainer:
const mapStateToProps = (state) => ({
showUserDetails: Boolean(state.user),
})
export default connect(mapStateToProps)(ParentComponent)
ParentComponent:
const ParentComponent = ({ showUserDetails }) => (
<div>
{showUserDetails && <ChildContainer />}
</div>
)
ChildContainer:
const mapStateToProps = (state) => ({
name: state.user.name,
})
export default connect(mapStateToProps)(ChildComponent)
ChildComponent:
const ChildComponent = ({ name }) => (
<h1>{name}></h1>
)
However I am running into scenarios where, after an action which sets state.user = null, ChildContainer tries to render before ParentContainer and hence throws an exception as it can't access null.name.
Is this expected behaviour of Redux, that a child container can re-render before the parent? In the classic React flow, this error would not happen.
I appreciate in this contrived example one could just call <ChildComponent name={user.name} /> in the ParentComponent instead of using a container. But in the real world I have deeply nested components accessing a lot of different parts of state, so can't just pass props down easily.
Are you batching your Redux dispatches? If not read v3.0.0 release notes carefully:
https://github.com/reactjs/react-redux/releases/tag/v3.0.0
It suggests redux-batched-updates module but it's more or less deprecated in favor of redux-batched-subscribe
Use it like this:
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom';
import { batchedSubscribe } from 'redux-batched-subscribe';
const store = createStore(reducer, intialState, batchedSubscribe(batchedUpdates));
The update will happen regardless of the child component being contained in a parent or not. Since it is in the DOM at the time of the update, the connect will try to update your component.
The easiest workaround for this would be to insert a safety check when pulling the user's name (state.user.name,) and replace it with (state.user && state.user.name,).
Or, of course - the alternative. Passing it down from the parent.
To further explain, the props that are being sent down from the parent will first be evaluated in the parent and then the updates would propagate downwards in the hierarchical tree. However, since your prop of name is actually coming directly from the state tree, you will need some safety checks around it.
Even if the parent after a subsequent update of it's own will decide that the child should no longer be rendered (since showUserDetails will now evaluate to false).