I have a question that what is the difference between use getState from store directly or use mapStateToProps. Please look at me example below
import React, { Component } from 'react'
import store from '../store'
import { connect } from 'react-redux';
class Test extends Component {
constructor(props) {
super(props);
}
render() {
return (
<p>
<h1>{this.props.count}</h1>
<h2>{store.getState().reducer1.count}</h2>
</p>
)
}
}
const mapStateToProps = (state) => ({
count: state.reducer1.count
});
// export default Test;
export default connect(mapStateToProps)(Test);
Both store.getState and mapStateToProps above work normally, it still updates when state change. If we just use getState only, we don't need to use connect method.
Another point I've recognized is when use mapStateToProps with connect, in reducer we must return a new copy of object state than return that state with modification. If not, component will not update when state changed. Like this:
return Object.assign({}, state, {
count: state.count + 1,
payload: action.payload,
});
But if we use store.getState(), we can either return a new copy or the revised one. Like this:
state.count++;
state.payload = action.payload;
return state
Anyone know please explain to me, thank you.
P/S: and similar with store.dispatch vs mapDispatchToProps, those 2 will work normally, just want to know why we should use mapToProps with connect instead of call the function directly from the store.
mapStateToProps is just a helper function which is really helpful to manage the project in modular style. For example, you can even place all the logic of connect in separate files and use where you want.
Suppose if you're working on a large scale application, then guess a sorts of properties nested there. Using connect you're actually modularizing project which is very helpful for developers who watch the project.
If you don't, you're writing several lines of code in single file.
A possible problem you'll face when using getState() or dispatch() directly. See this post for a little help to make it clear.
The key benefit using connect is that you don't need to worry about when state is changed using store.subscribe(), the connect will let you know each state change whenever it gets updates.
Also, react core concept is based on props and states. Using connect allows you to get redux state as props. Using this.props :)
And ah, I remembered at what condition I accessed the store directly rather than using connect. In my project, I needed to save all the redux state in different form to somewhere and I din't need to connect it to any component. In this case, direct usage with redux store is very easy and helpful. But if we try the same with connect in this case, then we'll have a difficult time.
Thus, I would suggest you to use them in separate condition.
Use connect if you want to map with component.
Access redux store directly if you don't need to map with component.
Further, this blog will explain a bit more: react redux connect explained
Redux Flow:
Using connect with react component:
To conclude: Using connect, you use the provider and it lets the every child component to access the store by providing a provider and using store props in root app component.
Related
While I can access the store using thunks and/or global stores, I don't want to bind my component to the Redux store. Because the component will be used with other stores both inside and outside the project. The component has a number of children and passes props to them via the context API.
Since context APIs cause rerendering on child components, we employ the below approach to pass data from redux store to context APIs. This way, I can use dataRef.current whenever I need to access data in the redux store:
export const useData = () => {
const dataRef = React.useRef();
useSelector((state) => {
dataRef.current = state;
});
return dataRef;
};
This works perfectly fine, But my concern is whether it will cause memory leaks or any other unknown problems.
It will likely cause things to not rerender in the right moment - since you leave the whole world of "letting React rerender" at that point in time. A change in the Redux store will not rerender your component any more.
I think you need to reevaluate why you are doing this. This technique seems to bind your component to the Redux store just as much as just calling useSeletor would do. As long as your state structure has the value in the path you expect, you could also just call useSelector directly without any weird workarounds. That would work with any Redux store being present at that point in time.
I'm building a Single App that will do SSR (server side rendering) and I'm using React + Redux.
I've just started to implement Redux in this app. It was previously built app using only React's useState, useContext etc.
The fact is that sometimes I need my app code to be aware of the environment that it's running, either ON_CLIENT or ON_SERVER, to skip some window.something statement, for example.
Before Redux, I was doing the following:
index.js (this could be the index.js of my client bundle or my server bundle)
ReactDOM.render(
<App
ON_SERVER={false} // THIS IS TRUE ON SERVER CODE
ON_CLIENT={true} // THIS IS TRUE ON CLIENT CODE
... other stuff
/>
,document.getElementById('root')
);
App.js
...
const environment = {
ON_SERVER: props.ON_SERVER,
ON_CLIENT: props.ON_CLIENT
}
...
// PROVIDING IT USING REACT CONTEXT
return (
<EnvironmentContext.Provider value={environment}>
<MyComponents/>
</EnvironmentContext.Provider>
);
And then, inside some component, I can do this pattern:
SomeComponent.js
const {ON_CLIENT} = useContext(EnvironmentContext);
ON_CLIENT && window.something;
And I want to improve this pattern with Redux.
I want to keep this in the Redux store, so I can get rid of the EnvironmentContext and access it with:
const {ON_CLIENT} = useSelector((state) => state.environment);
So I've thought of doing:
index.js
const store = createStore(rootReducer, {
environment: {
ON_CLIENT: true, // THIS IS TRUE ON CLIENT CODE
ON_SERVER: false // THIS IS TRUE ON SERVER CODE
}
});
But since I don't have a corresponding reducer for this piece of state (environment), I got this error msg:
redux.js:319 Unexpected key "environment" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: "auth", "appVersion", "siteData". Unexpected keys will be ignored.
NOTE: auth, appVersion and siteData are pieces of state which I have corresponding reducers for.
Here is my rootReducer:
const rootReducer = combineReducers({
auth: updateAuth,
appVersion: updateClientVersion,
siteData: updateSiteData
});
QUESTION
Can I have some piece of state that will not change, and therefore is not handled by any reducer? Or in this case I do need to set up some dummy reducer just to always return that same state? PS: It does the trick, but it feels wrong, though.
// NOTE: I will always preload this state, in the `createStore` call, so the state will never be undefined.
function returnEnvironment(state={}, action) {
return state;
}
const rootReducer = combineReducers({
auth: updateAuth,
appVersion: updateClientVersion,
siteData: updateSiteData,
environment: returnEnvironment
});
Does anybody have a better alternative to this?
I've looked at this discussion: https://github.com/reduxjs/redux/issues/1457
There are some suggestions to populate the global object, but I'd rather keep it all inside React and Redux.
PS: Sorry for the long question, but I wanted to make my use case as clear as I could, so somebody might have a better pattern.
Redux is designed for data centralisation and management such as loading some dynamic data changing it during the application lifetime and so on.Since you are not going to change that values because your application can’t run in both environments on a same time or switch between them that mean you don’t need to change them during application lifetime and if some variable should not change value it should be declared as a CONSTANT. So declare it as a constant and important whenever you need to access it.
When creating a React app, if I use the hook useSelector, I need to adhere to the hooks invoking rules (Only call it from the top level of a functional component). If I use the mapStateToProps, I get the state in the props and I can use it anywhere without any issues... Same issue for useDispatch
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
Redux store state can be read and changed from anywhere in the component, including callbacks. Whenever the store state is changed the component rerenders. When the component rerenders, useSelector runs again, and gives you the updated data, later to be used wherever you want. Here is an example of that and a usage of useDispatch inside a callback (after an assignment in the root level):
function Modal({ children }) {
const isOpen = useSelector(state => state.isOpen);
const dispatch = useDispatch();
function handleModalToggeled() {
// using updated data from store state in a callback
if(isOpen) {
// writing to state, leading to a rerender
dispatch({type: "CLOSE_MODAL"});
return;
}
// writing to state, leading to a rerender
dispatch({type: "OPEN_MODAL"});
}
// using updated data from store state in render
return (isOpen ? (
<div>
{children}
<button onClick={handleModalToggeled}>close modal</button>
</div>
) : (
<button onClick={handleModalToggeled}>open modal</button>
);
);
}
There is nothing you can do with mapStateToProps/mapDispatchToProps that you can't do with the useSelector and useDispatch hooks as well.
With that said, there are a couple of differences between the two methods that are worth considering:
Decoupling: with mapStateToProps, container logic (the way store data is injected into the component) is separate from the view logic (component rendering).
useSelector represents a new and different way of thinking about connected components, arguing that the decoupling is more important between components and that components are self contained. Which is better? Verdict: no clear winner. source
DX (Developer experience): using the connect function usually means there should be another additional container component for each connected component, where using the useSelector and useDispatch hooks is quite straightforward. Verdict: hooks have better DX.
"Stale props" and "Zombie child": there are some weird edge cases with useSelector, if it depends on props, where useSelector can run before the newest updated props come in. These are mostly rare and avoidable edge cases, but they had been already worked out in the older connect version. verdict: connect is slightly more stable than hooks. source
Performance optimizations: both support performance optimizations in different ways: connect has some advanced techniques, using merge props and other options hidden in the connect function. useSelector accepts a second argument - an equality function to determine if the state has changed. verdict: both are great for performance in advanced situations.
Types: using typescript with connect is a nightmare. I remember myself feverishly writing three props interfaces for each connected component (OwnProps, StateProps, DispatchProps). Redux hooks support types in a rather straightforward way. verdict: types are significantly easier to work with using hooks.
The future of React: Hooks are the future of react. This may seam like an odd argument, but change to the ecosystem is right around the corner with "Concurrent mode" and "Server components". While class components will still be supported in future React versions, new features may rely solely on hooks. This change will of course also affect third party libraries in the eco system, such as React-Redux. verdict: hooks are more future proof.
TL;DR - Final verdict: each method has its merits. connect is more mature, has less potential for weird bugs and edge cases, and has better separation of concerns. Hooks are easier to read and write, as they are collocated near the place where they are used (all in one self contained component). Also, they are easier to use with TypeScript. Finally, they will easily be upgradable for future react versions.
I think you misunderstand what "top level" is. It merely means that, inside a functional component, useSelector() cannot be placed inside loops, conditions and nested functions. It doesn't have anything to do with root component or components structure
// bad
const MyComponent = () => {
if (condition) {
// can't do this
const data = useSelector(mySelector);
console.log(data);
}
return null;
}
---
// good
const MyComponent = () => {
const data = useSelector(mySelector);
if (condition) {
console.log(data); // using data in condition
}
return null;
}
If anything, mapStateToPtops is located at even higher level than a hook call
the rules of hooks make it very hard to use that specific hook. You still need to somehow access a changing value from the state inside callbacks
To be fair you almost never have to access changing value inside a callback. I can't remember last time I needed that. Usually if your callback needs the latest state, you are better off just dispatching an action and then handler for that action (redux-thunk, redux-saga, redux-observable etc) will itself access the latest state
This is just specifics of hooks in general (not just useSelector) and there are tons of ways to go around it if you really want to, for example
const MyComponent = () => {
const data = useSelector(mySelector);
const latestData = useRef()
latestData.current = data
return (
<button
onClick={() => {
setTimeout(() => {
console.log(latestData.current) // always refers to latest data
}, 5000)
}}
/>
)
}
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
You save time by not writing connect function any time you need to access store, and removing it when you no longer need to access store. No endless wrappers in react devtools
You have clear distinction and no conflicts between props coming from connect, props coming from parent and props injected by wrappers from 3rd party libraries
Sometimes you (or fellow developers you work with) would choose unclear names for props in mapStateToProps and you will have to scroll all the way to mapStateToProps in the file to find out which selector is used for this specific prop. This is not the case with hooks where selectors and variables with data they return are coupled on the same line
By using hooks you get general advantages of hooks, the biggest of which is being able couple together and reuse related stateful logic in multiple components
With mapStateToProps you usually have to deal with mapDispatchToProps which is even more cumbersome and easier to get lost in, especially reading someone else's code (object form? function form? bindActionCreators?). Prop coming from mapDispatchToProps can have same name as it's action creator but different signature because it was overridden in mapDispatchToprops. If you use one action creator in a number of components and then rename that action creator, these components will keep using old name coming from props. Object form easily breaks if you have a dependency cycle and also you have to deal with shadowing variable names
.
import { getUsers } from 'actions/user'
class MyComponent extends Component {
render() {
// shadowed variable getUsers, now you either rename it
// or call it like this.props.getUsers
// or change import to asterisk, and neither option is good
const { getUsers } = this.props
// ...
}
}
const mapDispatchToProps = {
getUsers,
}
export default connect(null, mapDispatchToProps)(MyComponent)
See EDIT 2 at the end for the final answer
Since no one knows how to answer, it seems like the best answer is that you should NOT be using useselector when you need information in other places other than the root level of your component. Since you don't know if the component will change in the future, just don't use useselector at all.
If someone has a better answer than this, I'll change the accepted answer.
Edit: Some answers were added, but they just emphasize why you shouldn't be using useselector at all, until the day when the rules of hooks will change, and you'll be able to use it in a callback as well. That being said, if you don't want to use it in a callback, it could be a good solution for you.
EDIT 2: An answer with examples of all that I wanted was added and showed how useSelector and useDispatch are easier to use.
The redux state returned from the useSelector hook can be passed around anywhere else just like its done for mapStateToProps. Example: It can be passed to another function too. Only constraint being that the hook rules has to be followed during its declaration:
It has to be declared only within a functional component.
During declaration, it can not be inside any conditional block . Sample code below
function test(displayText) {
return (<div>{displayText}</div>);
}
export function App(props) {
const displayReady = useSelector(state => {
return state.readyFlag;
});
const displayText = useSelector(state => {
return state.displayText;
});
if(displayReady) {
return
(<div>
Outer
{test(displayText)}
</div>);
}
else {
return null;
}
}
EDIT: Since OP has asked a specific question - which is about using it within a callback, I would like to add a specific code.In summary, I do not see anything that stops us from using useSelector hook output in a callback. Please see the sample code below, its a snippet from my own code that demonstrates this particular use case.
export default function CustomPaginationActionsTable(props) {
//Read state with useSelector.
const searchCriteria = useSelector(state => {
return state && state.selectedFacets;
});
//use the read state in a callback invoked from useEffect hook.
useEffect( ()=>{
const postParams = constructParticipantListQueryParams(searchCriteria);
const options = {
headers: {
'Content-Type': 'application/json'
},
validateStatus: () => true
};
var request = axios.post(PORTAL_SEARCH_LIST_ALL_PARTICIPANTS_URI, postParams, options)
.then(function(response)
{
if(response.status === HTTP_STATUS_CODE_SUCCESS) {
console.log('Accessing useSelector hook output in axios callback. Printing it '+JSON.stringify(searchCriteria));
}
})
.catch(function(error) {
});
}, []);
}
For callback functions you can use the value returned from useSelector the same way you would use the value from useState.
const ExampleComponent = () => {
// use hook to get data from redux state.
const stateData = useSelector(state => state.data);
// use hook to get dispatch for redux store.
// this allows actions to be dispatched.
const dispatch = useDispatch();
// Create a non-memoized callback function using stateData.
// This function is recreated every rerender, a change in
// state.data in the redux store will cause a rerender.
const callbackWithoutMemo = (event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
};
// Create a memoized callback function using stateData.
// This function is recreated whenever a value in the
// dependency array changes (reference comparison).
const callbackWithMemo = useCallback((event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
}, [stateData, doSomething, doSomethingElse]);
// Use the callbacks.
return (
<>
<div onClick={callbackWithoutMemo}>
Click me
</div>
<div onClick={callbackWithMemo}>
Click me
</div>
</>
)
};
Rules of hooks says you must use it at the root of your component, meaning you CANT use it anywhere.
As Max stated in his answer just means that the hook statement itself must not be dynamic / conditional. This is because the order of the base hooks (react's internal hooks: useState, etc) is used by the backing framework to populate the stored data each render.
The values from hooks can be used where ever you like.
While I doubt this will be close to answering your complete question, callbacks keep coming up and no examples had been posted.
not the answer but this hook can be very helpful if you want to get decoupled nature of mapDispatchToProps while keeping simplicity and dev experience of hooks:
https://gist.github.com/ErAz7/1bffea05743440d6d7559afc9ed12ddc
the reason I don't mention one for mapStatesToProps is that useSelector itself is more store-logic-decoupling than mapStatesToProps so don't see any advantage for mapStatesToProps. Of course I dont mean using useSelector directly but instead create a wrapper on it in your store files (e.g. in reducer file) and import from there, like this:
// e.g. userReducer.js
export const useUserProfile = () => useSelector(state => state.user.profile)
I came across an example, where the mapStateToProps function is using memoization. I was just wondering how the "state" parameter is passed onto memoized selectors. After looking at the docs for reselect as well as redux, it seems that the mapStateToProps can return a function which accepts state as its argument, and the connect decorator might be the one passing the state to it but am not sure. Can someone please shed some light?
views/tracklist/index.js
const mapStateToProps = createSelector(
getBrowserMedia,
getPlayerIsPlaying,
getPlayerTrackId,
getCurrentTracklist,
getTracksForCurrentTracklist,
(media, isPlaying, playerTrackId, tracklist, tracks) => ({
displayLoadingIndicator: tracklist.isPending || tracklist.hasNextPage,
isMediaLarge: !!media.large,
isPlaying,
pause: audio.pause,
pauseInfiniteScroll: tracklist.isPending || !tracklist.hasNextPage,
play: audio.play,
selectedTrackId: playerTrackId,
tracklistId: tracklist.id,
tracks
})
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Tracklist);
core/tracklists/selectors.js
export function getCurrentTracklist(state) {
// console.log(state);
let tracklists = getTracklists(state);
return tracklists.get(tracklists.get('currentTracklistId'));
}
export const getTracksForCurrentTracklist = createSelector(
getCurrentPage,
getCurrentTrackIds,
getTracks,
(currentPage, trackIds, tracks) => {
return trackIds
.slice(0, currentPage * TRACKS_PER_PAGE)
.map(id => tracks.get(id));
}
);
Overview of how state is passed down to a selector when we use the Connect component from react-redux
What is a selector?
A selector extracts a subset of data from a source.
Let us think of the Redux store as our 'front end database'. For the purposeIn a database if you want to extract a subset of the total data you execute a query. In a similar fashion selectors are our queries to the Redux store.
In the simplest case, a selector could just return the state of the entire store.
The reselect docs give us three great reasons to use selectors
Selectors can compute derived data, allowing Redux to store the
minimal possible state.
Selectors are efficient. A selector is not
recomputed unless one of its arguments change.
Selectors are
composable. They can be used as input to other selectors.
What is a higher order component?
A higher-order component is a function that takes an existing component and returns a new component.
Connect is a higher order component that be given a selector
Taken from this brilliant gist which gives a good explanation of connect.
connect() is a function that injects Redux-related props into your
component.
Connect is a higher order component that makes our React component know about the Redux store. When we call connect we can pass mapStateToProps and mapDispatchToProps. These functions define the way in which our new component will be connected to the redux store.
We can give it access to state by passing a mapStateToProps function as an argument.
We can also bind action creators to store.dispatch through mapDispatchToProps. The advantage of this is that we don't need to pass down the entire store in order for a component to have access to store.dispatch so that the component can dispatch its own Redux actions.
The mapStateToProps function we pass to Connect is a selector
From the react-redux docs
The mapStateToProps function takes a single argument of the entire
Redux store’s state and returns an object to be passed as props. It is
often called a selector.
Think of the object that is returned by mapStateToProps as the result of our query to the Redux store. The resulting
The mapStateToProps function should normally return a plain object.
The result of calling mapStateToProps will normally be a plain object representing the data we extracted from the redux store.
The higher order Connect component allows us to extend the functionality of an existing component by merging in the data from this new object with the component's existing props.
Since selectors are just functions we can connect them to the Redux store using the connect component as well.
However in some cases we can return a function. Why would we do this?
By returing a function in mapStateToProps we can hijack the rendering cycle of components and optimise performance
In advanced scenarios where you need more control over the rendering
performance, mapStateToProps() can also return a function. In this
case, that function will be used as mapStateToProps() for a particular
component instance. This allows you to do per-instance memoization.
By passing the mapStateToProps function as an argument to our higher order component our connected component will be updated anytime the some state has changed in the Redux store.
If these updates happen very frequently or the state tree is large then the reselect library is useful as it allows us to use memoized selectors.
This fancy word means that results of selector calls are stored in case they need to be retrieved again.
So if mapStatesToProps returned a plain object instead of a function then whenever our store state changed then we would have new props for our component.???
Connecting selectors to the store
If you are using React Redux, you can call selectors as regular functions inside mapStateToProps():
import { getVisibleTodos } from '../selectors'
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
Sharing Selectors Across Multiple Components
We can give reselect selectors props just like components when using the reselect library. This allows us to share selectors across multiple components.
Say we have multiple toDo lists each with their own Id. We would still use the same getVisibleTodos selector for each toDo list instance but just pass a different id as a prop.
However the issue with this is that createSelector only returns the cached value when its set of arguments is the same as its previous set of arguments.
The reselect docs point out that we can overcome this limitation by returning a function inside mapStateToProps:
In order to share a selector across multiple components and retain
memoization, each instance of the component
needs its own private copy of the selector.
If the mapStateToProps argument supplied to connect returns a function
instead of an object, it will be used to create an individual
mapStateToProps function for each instance of the container.
By returning a function inside mapStateToProps we can overcome this limitation and memoization will work correctly.
For a more detailed explanation see this
Is so simple: let's give you an example, I have a mapStateToProps like this:
function mapStateToProps(state) {
return {
categoryHistory: getCategoryHistory(state,'extended')
}
}
then I've create a selector like this:
export const getCategoryHistory = (state, type) => createSelector([getTaxonomy, selectedCategoryID], (categories, categoryID) => categories.getIn([type, categoryID]) || [])(state)
The solution is to call createSelector() passing the state as parameters:
createSelector()(state)
inside the selector you can use all the parameter you want to pass through.
In the cases you mentioned, mapStateToProps is a function which takes in state and returning object. When you passed mapStateToProps to connect, you passed a function which accepts state provided by connect as its argument.
createSelector creates a function which can take in state and returning object as well. Therefore you can assign it to mapStateToProps and pass it into connect.
In documentation, you'll normally find the following:
const mapStateToProps = (state) => {
whatever code
}
and
export default connect(mapStateToProps, mapDispatchToProps)(Component)
where mapStateToProps takes in state argument which is provided by connect.
However, one can let mapStateToProps to be a selector as followed:
const mapStateToProps = createSelector(
whatever code
)
This is because createSelector can take in a state as followed:
createSelector(whatever code)(state)
and return an object, just like what you find a mapStateToProps does in documentation.
Example code: https://github.com/d6u/example-redux-update-nested-props/blob/master/one-connect/index.js
View live demo: http://d6u.github.io/example-redux-update-nested-props/one-connect.html
How to optimize small updates to props of nested component?
I have above components, Repo and RepoList. I want to update the tag of the first repo (Line 14). So I dispatched an UPDATE_TAG action. Before I implemented shouldComponentUpdate, the dispatch takes about 200ms, which is expected since we are wasting lots of time diffing <Repo/>s that haven't changed.
After added shouldComponentUpdate, dispatch takes about 30ms. After production build React.js, the updates only cost at about 17ms. This is much better, but timeline view in Chrome dev console still indicate jank frame (longer than than 16.6ms).
Imagine if we have many updates like this, or <Repo/> is more complicated than current one, we won't be able to maintain 60fps.
My question is, for such small updates to a nested component's props, is there a more efficient and canonical way to update the content? Can I still use Redux?
I got a solution by replacing every tags with an observable inside reducer. Something like
// inside reducer when handling UPDATE_TAG action
// repos[0].tags of state is already replaced with a Rx.BehaviorSubject
get('repos[0].tags', state).onNext([{
id: 213,
text: 'Node.js'
}]);
Then I subscribe to their values inside Repo component using https://github.com/jayphelps/react-observable-subscribe. This worked great. Every dispatch only costs 5ms even with development build of React.js. But I feel like this is an anti-pattern in Redux.
Update 1
I followed the recommendation in Dan Abramov's answer and normalized my state and updated connect components
The new state shape is:
{
repoIds: ['1', '2', '3', ...],
reposById: {
'1': {...},
'2': {...}
}
}
I added console.time around ReactDOM.render to time the initial rendering.
However, the performance is worse than before (both initial rendering and updating). (Source: https://github.com/d6u/example-redux-update-nested-props/blob/master/repo-connect/index.js, Live demo: http://d6u.github.io/example-redux-update-nested-props/repo-connect.html)
// With dev build
INITIAL: 520.208ms
DISPATCH: 40.782ms
// With prod build
INITIAL: 138.872ms
DISPATCH: 23.054ms
I think connect on every <Repo/> has lots of overhead.
Update 2
Based on Dan's updated answer, we have to return connect's mapStateToProps arguments return an function instead. You can check out Dan's answer. I also updated the demos.
Below, the performance is much better on my computer. And just for fun, I also added the side effect in reducer approach I talked (source, demo) (seriously don't use it, it's for experiment only).
// in prod build (not average, very small sample)
// one connect at root
INITIAL: 83.789ms
DISPATCH: 17.332ms
// connect at every <Repo/>
INITIAL: 126.557ms
DISPATCH: 22.573ms
// connect at every <Repo/> with memorization
INITIAL: 125.115ms
DISPATCH: 9.784ms
// observables + side effect in reducers (don't use!)
INITIAL: 163.923ms
DISPATCH: 4.383ms
Update 3
Just added react-virtualized example based on "connect at every with memorization"
INITIAL: 31.878ms
DISPATCH: 4.549ms
I’m not sure where const App = connect((state) => state)(RepoList) comes from.
The corresponding example in React Redux docs has a notice:
Don’t do this! It kills any performance optimizations because TodoApp will rerender after every action.
It’s better to have more granular connect() on several components in your view hierarchy that each only
listen to a relevant slice of the state.
We don’t suggest using this pattern. Rather, each connect <Repo> specifically so it reads its own data in its mapStateToProps. The “tree-view” example shows how to do it.
If you make the state shape more normalized (right now it’s all nested), you can separate repoIds from reposById, and then only have your RepoList re-render if repoIds change. This way changes to individual repos won’t affect the list itself, and only the corresponding Repo will get re-rendered. This pull request might give you an idea of how that could work. The “real-world” example shows how you can write reducers that deal with normalized data.
Note that in order to really benefit from the performance offered by normalizing the tree you need to do exactly like this pull request does and pass a mapStateToProps() factory to connect():
const makeMapStateToProps = (initialState, initialOwnProps) => {
const { id } = initialOwnProps
const mapStateToProps = (state) => {
const { todos } = state
const todo = todos.byId[id]
return {
todo
}
}
return mapStateToProps
}
export default connect(
makeMapStateToProps
)(TodoItem)
The reason this is important is because we know IDs never change. Using ownProps comes with a performance penalty: the inner props have to be recalculate any time the outer props change. However using initialOwnProps does not incur this penalty because it is only used once.
A fast version of your example would look like this:
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {Provider, connect} from 'react-redux';
import set from 'lodash/fp/set';
import pipe from 'lodash/fp/pipe';
import groupBy from 'lodash/fp/groupBy';
import mapValues from 'lodash/fp/mapValues';
const UPDATE_TAG = 'UPDATE_TAG';
const reposById = pipe(
groupBy('id'),
mapValues(repos => repos[0])
)(require('json!../repos.json'));
const repoIds = Object.keys(reposById);
const store = createStore((state = {repoIds, reposById}, action) => {
switch (action.type) {
case UPDATE_TAG:
return set('reposById.1.tags[0]', {id: 213, text: 'Node.js'}, state);
default:
return state;
}
});
const Repo = ({repo}) => {
const [authorName, repoName] = repo.full_name.split('/');
return (
<li className="repo-item">
<div className="repo-full-name">
<span className="repo-name">{repoName}</span>
<span className="repo-author-name"> / {authorName}</span>
</div>
<ol className="repo-tags">
{repo.tags.map((tag) => <li className="repo-tag-item" key={tag.id}>{tag.text}</li>)}
</ol>
<div className="repo-desc">{repo.description}</div>
</li>
);
}
const ConnectedRepo = connect(
(initialState, initialOwnProps) => (state) => ({
repo: state.reposById[initialOwnProps.repoId]
})
)(Repo);
const RepoList = ({repoIds}) => {
return <ol className="repos">{repoIds.map((id) => <ConnectedRepo repoId={id} key={id}/>)}</ol>;
};
const App = connect(
(state) => ({repoIds: state.repoIds})
)(RepoList);
console.time('INITIAL');
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('app')
);
console.timeEnd('INITIAL');
setTimeout(() => {
console.time('DISPATCH');
store.dispatch({
type: UPDATE_TAG
});
console.timeEnd('DISPATCH');
}, 1000);
Note that I changed connect() in ConnectedRepo to use a factory with initialOwnProps rather than ownProps. This lets React Redux skip all the prop re-evaluation.
I also removed the unnecessary shouldComponentUpdate() on the <Repo> because React Redux takes care of implementing it in connect().
This approach beats both previous approaches in my testing:
one-connect.js: 43.272ms
repo-connect.js before changes: 61.781ms
repo-connect.js after changes: 19.954ms
Finally, if you need to display such a ton of data, it can’t fit in the screen anyway. In this case a better solution is to use a virtualized table so you can render thousands of rows without the performance overhead of actually displaying them.
I got a solution by replacing every tags with an observable inside reducer.
If it has side effects, it’s not a Redux reducer. It may work, but I suggest to put code like this outside Redux to avoid confusion. Redux reducers must be pure functions, and they may not call onNext on subjects.