https://redux.js.org/recipes/computing-derived-data describes how to use reselect createSelector() to avoid unnecessary updates when a component depends on properties that are computed values.
An alternative seems to be to move the necessary computations into a (hopefully) lightweight container component that is connected to the store instead. In this case mapStateToProps() would return references to the store and connect() would not trigger an update of the container component unless the relevant values in the store has changed:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const mapStateToProps = state => {
return {
visibilityFilter: state.visibilityFilter,
todos: state.todos,
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const connected = connect(
mapStateToProps,
mapDispatchToProps
)
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const VisibleTodoList = connected((props) => {
return <TodoList
todos={getVisibleTodos(props.todos, props.visibilityFilter)}
onTodoClick={props.onTodoClick}
/>
})
export default VisibleTodoList
Are there any reasons for preferring reselect over a connected container component? I don't find (m)any examples discussing the above approach.
What if you need to use getVisibleTodos in more than one container?
Selectors decouple your containers from the store. When the store shape changes, you only need to update selectors to get the containers in working order.
For this reason, some would advocate placing selectors alongside the reducers.
Selectors are also useful during testing, allowing you to make assertions in terms of domain logic rather than storage logic. This makes tests less brittle.
It's useful to think of the selectors and actions as two halves of your public store API, and everything else as implementation details that are prone to change.
Also, for many simple cases (like your filtering example) you might want to choose a more humble memoizer function over the more ceremonious setup for createSelector():
const selectVisibleTodos = memoize(getVisibleTodos)
const mapStateToProps = state => ({
todos: selectVisibleTodos(state.todos, state.visibilityFilter),
})
vs
const selectVisibleTodos = createSelector(
(state) => state.todos,
(state) => state.visibilityFilter,
getVisibleTodos
)
const mapStateToProps = state => ({
todos: selectVisibleTodos(state),
})
Pros:
More concise code
Faster than createSelector (Less internal overhead)
Cons:
Less separation between mapSateToProps and the shape of the redux state
I personally tend to keep both createSelector and a memoize function at hand, and choose between them on a case by case basis.
Related
I'm working on a React project where I'm constrained to using React Redux v5, which doesn't include useDispatch and useSelector.
Nonetheless I really would like to have these hooks (or something like them) available in my app.
Therefore, I've created a wrapper component at the top level of the app which I connect using redux's connect function.
My mapStateToProps and mapDispatchToProps then just look like this:
const mapDispatchToProps = (dispatch: DispatchType) => {
return {
dispatch,
};
};
const mapStateToProps = (state: StateType) => {
return {
state,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MainLayout);
In my wrapper component, I then pass the dispatch and the state into the value:
<DispatchContext.Provider value={{ state, dispatch }}>
{children}
</DispatchContext.Provider>
Finally, I have a hook that looks like this:
const useSelectAndDispatch = () => {
const context = useContext(DispatchContext);
if (context === null) {
throw new Error("Please use useDispatch within DispatchContext");
}
const { state, dispatch } = context;
function select(selector) {
return selector(state);
}
return { dispatch, select };
};
I then use dispatch and selector in my components via useSelectAndDispatch.
I was wondering if this is an appropriate way to go about this issue, and whether I can expect any performance problems. I am using reselect, and have a good understanding of memoization. I'm just looking for opinions, since I've heard that the redux team held off implementing useDispatch and useSelector for a long time because of performance issues.
Many thanks for any opinions!
This will cause significant peformance problems. Your mapStateToProps is using the entire state object, so every time anything changes in the state, the provider must rerender. And since the provider rerendered with a new value, so too must every component that consumes the context. In short, you will be forcing most of your app to rerender anytime anything changes.
Instead of using mapStateToProps and mapDispatchToProps, i would go back to the actual store object, and build your hooks from that. Somewhere in your app is presumably a line of code that says const store = createStore(/* some options */).
Using that store variable, you can then make some hooks. If i can assume that there's only one store in your app, then the dispatch hook is trivial:
import { store } from 'wherever the store is created'
export const useMyDispatch = () => {
return store.dispatch;
}
And the selector one would be something like this. It uses .subscribe to be notified when something in the store changes, and then it uses the selector to pluck out the part of the state that you care about. If nothing changed, then the old state and new state will pass an === check, and react skips rendering. If it has changed though, the component renders (only the component that called useMySelect plus its children, not the entire app)
export const useMySelector = (selector) => {
const [value, setValue] = useState(() => {
return selector(store.getState());
});
useEffect(() => {
const unsubscribe = store.subscribe(() => {
const newValue = selector(store.getState());
setValue(newValue);
});
return unsubscribe;
}, []);
return value;
}
Based on all the Redux and Reselect docs I have just read and re-read the below selector should only do the thing.toJS() processing if the Immutable Map that getThing() returns is not equal to the previous one.
...
// Selector
import { createSelector } from 'reselect'
const getThing = (state, thingId) => {
return state.entities.getIn(['things', thingId])
}
const makeThingSelector = () => {
return createSelector(
[getThing],
(thing) => {
return thing.toJS()
}
)
}
export default makeThingSelector
...
// Container
const makeMapStateToProps = () => {
return (state, ownProps) => {
const { thingId } = ownProps
const things = select.makeThingsSelector()(state, thingId)
return {
hasNoThings: things.length === 0,
things
}
}
}
const Container = connect(
makeMapStateToProps,
mapDispatchToProps
)(Component)
...
This holds true unless I have a child 'smart' component. In this case, when the parent triggers a render, the selector called in the child component's container always processes the value regardless of whether the result is new or not.
I have been trying to encapsulate the ImmutableJS API inside my selectors but this means to avoid a re render on these nested components every time their parents update I have to do a deep equality check in the shouldComponentUpdate function. This is expensive and doesn't seem like a decent solution.
The app state is normalised so the updated part of the state tree is not a hierarchical parent to the part of the state that the child component is dependent on.
Am I missing something key here?
On every store update react-redux performs following steps (putting all internal complexities aside):
Calls mapStateToProps and mapDispatchToProps.
Shallowly comparing resulted props
Re-renders Component In case new props differs from previous one.
This way mapStateToProps will be called on every store update by-design. So will following line of code:
...
const things = select.makeThingsSelector()(state, visitId)
...
As you can see new reselect selector will be created every time effectively preventing any memoization (there are no global state in reselect, memoization happens per selector).
What you have to do is change your code so that one and the same selector will be used on every invocation of mapStateToProps:
const thingSelector = select.makeThingsSelector();
...
const makeMapStateToProps = () => {
return (state, ownProps) => {
const { visitId } = ownProps
const things = thingSelector(state, visitId)
return {
hasNoThings: things.length === 0,
things
}
}
}
UPDATE: Also I don't see any reason to use factory-style makeThingsSelector and makeMapStateToProps. Why not just go with something like:
...
// Selector
export default createSelector(
[getThing],
(thing) => thing.toJS()
);
...
// Container
const mapStateToProps = (state, ownProps) => {
const { visitId } = ownProps
const things = select.thingsSelector(state, visitId)
return {
hasNoThings: things.length === 0,
things
}
}
const Container = connect(
mapStateToProps,
mapDispatchToProps
)(Component)
...
Since the redux state in this application uses the ImmutableJS data structure, Reselect may not be necessary.
Firstly, ImmutableJS manipulates only the slice of the data structure affected by a change operation and therefore all changes to the larger state may not affect the slice being passed to the container.
Secondly, the redux connect function returns a pure container by default and upon encountering same slice will not re-render. However, the mapStateToProps will be invoked since the whole state and possibly the ownProps have changed.
For finer control, the rendering of same container can be linked directly to changes to a particular slice of the state and ownProps by adding areStatesEqual and areOwnPropsEqual predicate properties to the fourth parameter of the connect function (better known as the options object).
const mapStateToProps = ({ entities }, { thingId }) => {
const things = entities.getIn(['things', thingId]).toJS();
return {
hasNoThings: things.length === 0,
things
};
};
const Container = connect(
mapStateToProps,
mapDispatchToProps,
undefined, {
areOwnPropsEqual: (np, pp) => np.thingId === pp.thingId,
areStatesEqual: (ns, ps) => ns.entities.get(‘things’).equals(
ps.entities.get(‘things’)
)
}
)(Component);
If both of these predicates are true, not only would the container and its children not re-render, the mapStateToProps would not even be invoked!
I do not know the correct way to pre-load data from API for a component to use.
I have written a stateless component which should render the data:
import React, { PropTypes } from 'react';
const DepartmentsList = ({ departments }) => {
const listItems = departments.map((department) => (
<li>{department.title}</li>
));
return (
<ul>
{listItems}
</ul>
);
};
DepartmentsList.propTypes = {
departments: PropTypes.array.isRequired
};
export default DepartmentsList;
And I have an action which will retreive data from the API:
import { getDepartments } from '../api/timetable';
export const REQUEST_DEPARTMENTS = 'REQUEST_DEPARTMENTS';
export const RECEIVE_DEPARTMENTS = 'RECEIVE_DEPARTMENTS';
const requestDepartments = () => ({ type: REQUEST_DEPARTMENTS });
const receiveDepartments = (departments) => ({ type: RECEIVE_DEPARTMENTS, departments });
export function fetchDepartments() {
return dispatch => {
dispatch(requestDepartments);
getDepartments()
.then(departments => dispatch(
receiveDepartments(departments)
))
.catch(console.log);
};
}
Now I think I have a few options to preload departments that are required for the list. I could use redux-thunk and mapDispatchToProps to inject fetchDepartments to the stateless component and implement componentWillMount or similar lifecycle method, to load data - but then I don't need to pass the list via props, as the component would always load data for himself, and I don't want that, because whenever a new component is created the data is fetched from api instead of store...
Another advice I've seen is to use getComponent function from react-router, and retreive all data before returning the component, however, I am not sure if it's the correct redux way, as I don't see how to use redux-thunk there, and logic kind of seems littered all accross the files, when it's the data required for only one component.
This leaves me with the only seemingly ok option to load data in container component's lifecycle methods, but I want to know what is considered the best practice for what I want to do.
The most 'redux-like' way of handling the pre-loading of data would be to fire off the asynchronous action in the lifecycle method (probably componentWillMount) of a Higher Order Component that wraps your app. However, you will not use the results of the API call directly in that component - it needs to be handled with a reducer that puts it into your app store. This will require you to use some sort of a thunk middleware to handle the asynchronous action. Then you will use mapStateToProps to simply pass it down to the component that renders the data.
Higher Order Component:
const mapStateToProps = (state) => {
return {
departments: state.departments
};
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
getDepartments: actionCreators.fetchDepartments
});
}
class App extends Component {
componentWillMount() {
this.props.getDepartments();
}
render() {
return <DepartmentsList departments={this.props.departments} />
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
reducers:
export function departments(state = [], action) {
switch(action.type) {
case 'RECEIVE_DEPARTMENTS':
return action.departments;
}
}
I am trying to follow this code in redux-saga
export const getUser = (state, login) => state.entities.users[login]
export const getRepo = (state, fullName) => state.entities.repos[fullName]
Which is then used in the saga like this:
import { getUser } from '../reducers/selectors'
// load user unless it is cached
function* loadUser(login, requiredFields) {
const user = yield select(getUser, login)
if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) {
yield call(fetchUser, login)
}
}
This getUser reducer (is it even a reducer) looks very different from what I would normally expect a reducer to look like.
Can anyone explain what a selector is and how getUser is a reducer and how it fits in with redux-saga?
getUser is not a reducer, it is indeed a selector, that is, a function that knows how to extract a specific piece of data from the store.
Selectors provide an additional layer such that if you altered your store structure and all of a sudden your users were no longer at state.entities.users but instead at state.users.objects.entities (or whatever) then you only need to update the getUser selector and not every place in your app where you were making a reference to the old location.
That makes them particularly handy when it comes to refactoring your Redux store.
Selectors are getters for the redux state. Like getters, selectors encapsulate the structure of the state, and are reusable. Selectors can also compute derived properties.
You can write selectors, such as the ones you saw in redux-saga. For example:
const getUsersNumber = ({ users }) => users.length;
const getUsersIds = ({ users }) => users.map(({ id }) => id);
etc...
You can also use reselect, which is a simple “selector” library for Redux, that memoize selectors to make them more efficient.
Selectors are functions that take Redux state as an argument and return some data to pass to the component.
const getUserData = state => state.user.data;
Why should it be used?
One of the main reasons is to avoid duplicated data in Redux.
Your data object shape keeps varying as your application grows, so rather than making changes in all the related component.It is much recommended/easier to change the data at one place.
Selectors should be near reducers because they operate on the same state. It is easier for data to keep in sync.
Using reselect helps to memoize data meaning when the same input is passed to the function, returns the previous result rather than recalculating again.So, this enhances your application performance.
function mapStateToProps (state) {
return {
user: state.user,
}
}
initialState of reducer by user store
const initialState = {
isAdmin:false,
isAuth:false,
access:[1,2,5]
};
class AppComp extends React.Component{
render(){
const {user: { access:access}} = this.props;
const rand = Math.floor(Math.random()*4000)
return (<div>
{`APP ${rand} `}
<input type="button" defaultValue="change auth" onClick={this.onChangeUserAuth} />
<p>TOTAL STATUS COUNT IS {access.length}</p>
</div>)
}
}}
but you can use selector
var getUser = function(state) {
return state.user
}
const getAuthProp = createSelector(
getUser,
(user) => user.access
);
function mapStateToProps (state) {
return {
// user: state.user,
access: getAuthProp(state)
}
}
Main Problem is this component use all user: state.user and any changes in user (etc isAdmin ,isAuth, access) runs rerender this component which need only part of this store - access!!!
In Redux, whenever an action is called anywhere in the application,
all mounted & connected components call their mapStateToProps
function. This is why Reselect is awesome. It will just return the
memoized result if nothing has changed.
In the real world, you will most likely need the same certain part of
your state object in multiple components.
https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
The createSelector function provided by Reselect implements the most basic way to derive a selector from previous selectors. The simplest use case is to derive a selector from a single other selector. In this case, the parameters to createSelector are the input selector and a function transforming the result of that selector into the result of the new selector. For example
var getProducts = function(state) {
return state.products
}
import {getProducts} from '../app/selectors'
import {createSelector} from 'reselect'
export const getProductTitles = createSelector(
getProducts,
(products) => products.map((product) => product.get('title'))
)
This is equivalent to (ignoring memoization):
import {getProducts} from '../app/selectors'
export const getProductTitles = (state) => {
return getProducts(state).map((product) => product.get('title'))
}
The createSelector function can combine data from multiple selectors as well as from a single selector. We can pass any number of selectors to createSelector, and their results will be passed to the function passed as the final argument. For a (somewhat contrived) example:
const isInCheckout = createSelector(
getIsShippingPage,
getIsBillingPage,
getIsConfirmationPage,
(isShipping, isBilling, isConfirmation) =>
isShipping || isBilling || isConfirmation
)
is equivalent to
const isInCheckout = (state) => {
return (
getIsShippingPage(state) ||
getIsBilingPage(state) ||
getIsConfirmationPage(state)
)
}
common pattern when writing mapStateToProps functions with selectors is to return an object with each key storing the result of a particular selector. The createStructuredSelector helper function in Reselect lets us write this pattern with the minimum of boilerplate. For example, if we writ
const mapStateToProps = createStructuredSelector({
title: getProductTitle,
price: getProductPrice,
image: getProductImage
})
it is equivalent to
const mapStateToProps = (state) => {
return {
title: getProductTitle(state),
price: getProductPrice(state),
image: getProductImage(state)
}
}
https://docs.mobify.com/progressive-web/0.15.0/guides/reselect/
I was wondering if there was still a point using mapDispatchToProps today.
I'm working on the redux documentation tutorials (to build a todo list) where VisibleTodoList is described as:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
However, I've been told that today, I could simply not to define mapDispatchToProps and connect everything through:
const VisibleTodoList = connect(
mapStateToProps,
toggleTodo
)(TodoList)
Is it right? And if so, what is the point to write a mapDispatchToProps? Is there any drawbacks to simply returning the action?
Thanks!
To clarify the other Mark's comment:
The second argument to connect() can take two main forms. If you pass a function as the argument, connect() assumes you want to handle dispatch preparation yourself, calls your function with dispatch as an argument, and merges the result into the props for your component.
If you pass in an object as the second argument to connect(), it assumes you've given it a map of prop names to action creators, and so it automatically runs all of them through the bindActionCreators utility and uses the result as props.
However, passing a single action creator as the second argument, as your example appears to do, would not do what you want, as connect() would interpret that as a preparation function and not an action creator that needs to be bound.
So yes, connect() supports a shorthand syntax of passing in an object full of action creators as the second argument, but there are still good use cases for passing in an actual mapDispatchToProps function to do things yourself (especially if your dispatch prep relies on the actual prop values in some way).
You might want to refer to the API docs for `connect().
connect() will automatically bind dispatch to your actions if they are passed in as an object of function names.
So no, you don't need to implement mapStateToProps. Instead you can just pass you actions like this:
export default connect((state) => state, {
action1,
action2,
})(MyComponent);