Do selectors cause re-rendering on unrelated components in Redux toolkit? - reactjs

I am diving into Redux toolkit and made a simple project to understand its concepts better.
function App() {
//..
return(
<UserList /> // uses submittedUserSelector
<UserInformation /> // uses selectedUserSelector
);
}
The basic idea is that you can either add a user to UserList or select one from the list which will be shown in UserInformation. Both the submitted and selected user are managed by different reducers in the same store.
export const rootReducer = combineReducers({
form: formReducer, // have submitted user selector
user: userReducer // have selected user selector
});
// formReducer.tsx
export const submittedUserSelector = (state:RootState)=>state.form.submittedUser; //object
// selectedUserReducer.tsx
export const selectedUserSelector = (state:RootState)=>state.user.selectedUser; //object
According to the official documentation and I quote:
When an action is dispatched, useSelector() will do a reference
comparison of the previous selector result value and the current
result value. If they are different, the component will be forced to
re-render. If they are the same, the component will not re-render.
So I was expecting that when I selected a user which dispatches an action in userReducer
would result in re-rendering UserList as well (as submittedUserSelector returns an object). That didn't happen, however.
Why? Does redux-toolkit figure out which components in the tree are using a particular selector and only evaluate those? Is that somewhere in the documentation?

Selectors will be called on every state change and only cause a component rerender if the selector result changes from the last render - so usually, unrelated components will not be rerendered.
Keep in mind that general React component rendering still applies - if a parent component rerenders, all child components will be rerendered.
This has nothing to do with redux-toolkit though - it is the normal behaviour of the react-redux hooks. See https://react-redux.js.org/api/hooks

Related

Can I update a property in the redux store without triggering a re-render?

I want to keep some data in the redux store that does not affect React components at all. However, each time I update the data, React re-renders the app. Is there a way to not trigger the re-render?
Updating data in redux will only trigger a re-render to a component if the data that specific component is "subscribing" to has changed.
For example, consider the following mapStateToProps.
const mapStateToProps = state => {
return {
cars: state.cars,
}
}
The component which defined this mapStateToProps will only render if the cars array is not the same as it was the last time the function ran. Now, if you have a piece of data in the store which is not defined in any mapStateToProps, no component will render as as result of changing that data point.
Here is the documentation for connect and mapStateToProps.

Benefits of redux

I've started learning React without Redux or Flux and have been hearing a lot about Redux and how it seems to be the favourable pattern to use for managing state going forward. My understanding of it is that the entire state of the App lives in the store which I believe is at the top of the React tree. The various child components then 'subscribe' to various states that are relevant to them.
This is somewhat confusing for me as I thought the core structure of React is already setup in this way? Ie if my component has a certain state then to pass it down to its child components in order to use if further down the React tree I would need to add in this.state.example or this.props.example to a component. To me with this approach i'm 'subscribing' the component in a way as well..
Apologies if this is not the right place for questions like this but if someone could tell me what i'm missing here or the added benefit of Redux that would be very helpful!
You are on the right track on the subscribing portion, but what makes Redux wonderful and many other Flux like state management patterns is that you don't have to pass properties down the child chain just so you could update a childs component like so (you could if you wanted to, but not needed):
function Parent() {
return <ChildOne color="red" />
}
function ChildOne(props) {
return <ChildTwo color={props.color} />
}
function ChildTwo(props) {
return <h1>The Color was: {props.color}</h1>
}
It allows you to "dispatch" (a redux/flux term) an action to the state store to update a property on whichever object a component may be subscribed to.
A helpful library for that "connection" is react-redux. It has many capabilities, but the main that I see is connect which is a higher ordered component (HOC) that "wraps" your component with more logic including the part of the redux store that you want to subscribe to.
So the above could be:
export class Parent extends React.Component {
componentDidMount() {
this.props.dispatch(changeColor('red'));
}
render() {
return <ChildOne />
}
}
export default connect((state) => ({ //This property is the redux store
parent: state.parent,
}))(Parent) //higher order component that wraps the component with redux functionality
function ChildOne(){
return (
<ChildTwo />
);
}
export function ChildTwo(props) { //will have childTwo bound in props object
return (
<h1>The Color is: {props.childTwo.color}
);
}
export default connect((state) => ({ //This property is the redux store
childTwo: state.childTwo,
}))
Where the biggest difference is that you didn't have to pass the color from Parent down 2 levels to ChildTwo because it was "subscribed" to the childTwo object within the redux store and you connected that bit of state to the component so any change to the store will trigger the component to rerender from the state change.
The passing of properties and using Redux will make more sense with this medium post of Presentation and Container components, where passing of properties makes sense as you are only going down one child layer deep and the container component is handling logic such as ajax requests, or dispatches to parts of the redux store, etc.

How does a redux connected component know when to re-render?

I'm probably missing something very obvious and would like to clear myself.
Here's my understanding.
In a naive react component, we have states & props. Updating state with setState re-renders the entire component. props are mostly read only and updating them doesn't make sense.
In a react component that subscribes to a redux store, via something like store.subscribe(render), it obviously re-renders for every time store is updated.
react-redux has a helper connect() that injects part of the state tree (that is of interest to the component) and actionCreators as props to the component, usually via something like
const TodoListComponent = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
But with the understanding that a setState is essential for the TodoListComponent to react to redux state tree change(re-render), I can't find any state or setState related code in the TodoList component file. It reads something like this:
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
Can someone point me in the right direction as to what I am missing?
P.S I'm following the todo list example bundled with the redux package.
The connect function generates a wrapper component that subscribes to the store. When an action is dispatched, the wrapper component's callback is notified. It then runs your mapState function, and shallow-compares the result object from this time vs the result object from last time (so if you were to rewrite a redux store field with its same value, it would not trigger a re-render). If the results are different, then it passes the results to your "real" component" as props.
Dan Abramov wrote a great simplified version of connect at (connect.js) that illustrates the basic idea, although it doesn't show any of the optimization work. I also have links to a number of articles on Redux performance that discuss some related ideas.
update
React-Redux v6.0.0 made some major internal changes to how connected components receive their data from the store.
As part of that, I wrote a post that explains how the connect API and its internals work, and how they've changed over time:
Idiomatic Redux: The History and Implementation of React-Redux
My answer is a little out of left field. It sheds light on a problem that led me to this post. In my case it seemed the app was Not re-rendering, even though it received new props.
React devs had an answer to this often asked question something to the tune that if the (store) was mutated, 99% of the time that's the reason react won't re-render.
Yet nothing about the other 1%. Mutation was not the case here.
TLDR;
componentWillReceiveProps is how the state can be kept synced with the new props.
Edge Case: Once state updates, then the app does re-render !
It turn out that if your app is using only state to display its elements, props can update, but state won't, so no re-render.
I had state that was dependent on props received from redux store. The data I needed wasn't in the store yet, so I fetched it from componentDidMount, as is proper. I got the props back, when my reducer updated store, because my component is connected via mapStateToProps. But the page didn't render, and state was still full of empty strings.
An example of this is say a user loaded an "edit post" page from a saved url. You have access to the postId from the url, but the info isn't in store yet, so you fetch it. The items on your page are controlled components - so all the data you're displaying is in state.
Using redux, the data was fetched, store was updated, and the component is connected, but the app didn't reflect the changes. On closer look, props were received, but app didn't update. state didn't update.
Well, props will update and propagate, but state won't.
You need to specifically tell state to update.
You can't do this in render(), and componentDidMount already finished it's cycles.
componentWillReceiveProps is where you update state properties that depend on a changed prop value.
Example Usage:
componentWillReceiveProps(nextProps){
if (this.props.post.category !== nextProps.post.category){
this.setState({
title: nextProps.post.title,
body: nextProps.post.body,
category: nextProps.post.category,
})
}
}
I must give a shout out to this article that enlightened me on the solution that dozens of other posts, blogs, and repos failed to mention. Anyone else who has had trouble finding an answer to this evidently obscure problem, Here it is:
ReactJs component lifecycle methods — A deep dive
componentWillReceiveProps is where you'll update state to keep in sync with props updates.
Once state updates, then fields depending on state do re-render !
This answer is a summary of Brian Vaughn's article entitled You Probably Don't Need Derived State (June 07, 2018).
Deriving state from props is an anti-pattern in all its forms. Including using the older componentWillReceiveProps and the newer getDerivedStateFromProps.
Instead of deriving state from props, consider the following solutions.
Two best practice recommendations
Recommendation 1. Fully controlled component
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}
Recommendation 2. Fully uncontrolled component with a key
// parent class
class EmailInput extends Component {
state = { email: this.props.defaultEmail };
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}
// child instance
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
Two alternatives if, for whatever reason, the recommendations don't work for your situation.
Alternative 1: Reset uncontrolled component with an ID prop
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}
Alternative 2: Reset uncontrolled component with an instance method
class EmailInput extends Component {
state = {
email: this.props.defaultEmail
};
resetEmailForNewUser(newEmail) {
this.setState({ email: newEmail });
}
// ...
}
As I know only thing redux does, on change of store's state is calling componentWillRecieveProps if your component was dependent on mutated state and then you should force your component to update
it is like this
1-store State change-2-call(componentWillRecieveProps(()=>{3-component state change}))

Reusable react-redux container components

In my React/Redux app I am often facing with the problem of implementing components with state which should be used throughout the app.
Let's take simple popup component as an example with open/close state which can be reused in any page.
Here is two possible approaches I found:
Use setState and "local" reducer (I use recompose.withReducer which is just syntax sugar for React's native setState) to manage its state. It looks easy and reusable until you need change the component's state in the other part of your page (close popup in out case). And you cannot just call some redux action to change the state.
Keep the component's state in the Redux store. With such approach we can call closePopupAction({ id }) in any place of the components tree to change it's state.` But we need somehow put its reducer (which i want to keep in the popup's folder) to the root reducer when the component is mounted and delete it when the component is unmounted. Plus we can have multiples popups in the page and each of them have its own state.
Did anybody face with a similar problem ?
I think you should keep state of component in redux. You can create reducer for this component and use combineReducers function in this way:
rootReducer = combineReducers({
moduleA: combineReducers({
popupA: popupReducer("id1"),
popupB: popupReducer("id2")
}),
moduleB: combineReducers({
popupA: popupReducer("id3")
})
})
});
then when you call closePopupAction("id1") reducer can check id and change proper part of state.
PS: Id should be provided in better way :)
You could mount each component's state to its own slice of the store.
So your closePopupAction actions would be called with that mount path:
closePopupAction({ mountPath: 'popups.popup1' })
and you would need only one reducer to handle all closePopupAction actions, which could be registered once at startup:
(state, { type, mountPath }) => {
if (type === 'CLOSE_POPUP_ACTION') {
// manipulate with the slice at `mountPath`, e.g.
return _.set(_.cloneDeep(state), `${mountPath}.isOpen`, false)
}
// ...
}

Limit Redux to update only components affected by the change

trying to understand React-Redux, i find it unusual that all my components get new props when ever any slice of the state get changed. so is this by design or i'm doing something wrong ?
example App
class App extends React.Component {
render(){return (
<div>
<Navbar data={this.props.navbar} />
<Content data={this.props.content} />
</div>);
}
}
select (state) => ({ navbar:state.navbar, content:state.content});
export default connect(select)(App);
Components
export const NavbarForm = props => {
console.log('RENDERING with props--->',props);
return (<h1>NAV {props.data.val}</h1>);
};
export const ContentForm = props => {
console.log('RENDERING CONTENT with props--->',props);
return (<h1>CONTENT {props.data.val}</h1>);
};
////////INDEX.js//////
const placeholderReducer = (state={val:0},action)=>{
//will update val to current time if action start with test/;
if(action.type.indexOf('TEST/') === 0)return {val:Date.now();}
return state;
}
export const rootReducer = combineReducers({
navbar:placeholderReducer,
content: (state,action)=>(state || {}), //**this will never do a thing.. so content should never updates right !!**
});
const store = createStore(rootReducer, {}, applyMiddleware(thunk));
render( <Provider store={store}> <App /></Provider>, document.getElementById('app')
);
setInterval(()=>{ store.dispatch(()=>{type:'TEST/BOOM'}) },3000);
okay in this app, what i expect is that Navbar component will get updated every 3000ms while content component will never updates because its reducer will always return the same state.
yet i find it really strange that both components does reRender every time an action is fired.
is this by design ? should i worry about performance if my app has 100+ component ?
This is entirely by design. React assumes that your entire app will be re-rendered from the top down by default, or at least a given subtree will be re-rendered if a certain component does a setState or something similar.
Because you only have the very top component in your app connected, everything from there on down is React's standard behavior. A parent component re-renders, causing all of its children to re-render, causing all of their children to re-render, and so on down.
The core approach to improving UI performance in React is to use the shouldComponentUpdate lifecycle method to check incoming props and return false if the component does not need to re-render. This will cause React to skip re-rendering that component and all of its descendants. Comparisons in shouldComponentUpdate are generally done using shallow reference equality, which is where the "same object references means don't update" thing becomes useful.
When using Redux and connect, you will almost always find yourself using connect on many different components in your UI. This provides a number of benefits. Components can individually extract the pieces of the store state that they need, rather than having to hand them all down from the root component. In addition, connect implements a default shouldComponentUpdate for you, and does a similar check on the values you return from your mapStateToProps function. So, in a sense, using connect on multiple components tends to give you a "free win" in regards to performance.
Further reading on the topic:
Redux FAQ: Connecting multiple components
React/Redux Links: Performance articles
Yes this is by design. Action is dispatched. Reducers run. Store subscribers get notified "the store has changed". Connected components are store subscribers.
Typically you just don't worry about it until you can actually measure a performance problem that you can attribute to this - don't prematurely optimize.
If you find out that it is a problem, then you can do one of the following:
Add a shouldComponentUpdate method to your components so they can see that the props they received aren't different and do not need to render (there are lots of Pure Render mixins & high order components available to make this easy)
Instead of connecting the top-level app, connect the Navbar and Content components directly. The App will never rerender, but the children will if the store changes. And react-redux automatically uses shouldComponentUpdate to only re-render the connected components that actually have new props.

Resources