So I have been looking into https://codesandbox.io/s/9on71rvnyo to understand how Redux works. I got to the part components/VisibilityFilters.js. I see on setFilter(currentFilter), what calls an action in redux/actions.js. But for me the understanding stops here. I don't understand how this action connects with the reducers. This just an function call!? Does
export default connect(
mapStateToProps,
{ setFilter }
)(VisibilityFilters);
do all the magic?
The first thing is that connect() makes a connection between your component and your Redux store. That's why you are exporting as connect(mapStateToProps, { actionName })(ComponentName);. As the connect() documentation states:
The connect() function connects a React component to a Redux store. It provides its connected component with the pieces of the data it needs from the store, and the functions it can use to dispatch actions to the store.
Thus from you component you are calling the function - actions - what you created which are dispatching with dispatch() a state change. As dispatch() documentation states:
Dispatches an action. This is the only way to trigger a state change. The store's reducing function will be called with the current getState() result and the given action synchronously. Its return value will be considered the next state. It will be returned from getState() from now on, and the change listeners will immediately be notified.
In the reducer based on the dispatch({type: 'STRING', payload: 'your data'}) the switch statement will find the proper type to change the state. At the end from your reducer the returned value will be causing a rerender in your component.
With a fairly simple draw what I just made:
+1 important:
Sometimes I see that developers are missing out the return value from the reducer which causes issues. There are 2 important things to note from Handling Actions documentation:
We don't mutate the state. We create a copy with Object.assign(). Object.assign(state, { visibilityFilter: action.filter }) is also wrong: it will mutate the first argument. You must supply an empty object as the first parameter. You can also enable the object spread operator proposal to write { ...state, ...newState } instead.
We return the previous state in the default case. It's important to return the previous state for any unknown action.
I hope that clarifies!
Related
I'm working on a React application that uses the following architecture:
redux
typesafe-actions
redux-observable
My question is: How can I execute an UI action on specific redux action?
For example, suppose we have the following async actions defined with typesafe-actions:
export const listTodo = createAsyncAction(
'TODO:LIST:REQUEST',
'TODO:LIST:SUCCESS',
'TODO:LIST:FAILURE',
)<void, Todo[], Error>();
An Epic will watch for listTodo.request() and send the API call, then convert the response to a listTodo.success() action. Then the redux reducer will be triggered by listTodo.success() action and store the todo list into redux store.
In this setting, suppose I want to do the following things in an component:
dispatch a listTodo.request() action to retrieve all the actions
After the async request is done (i.e. after listTodo.success() action appears in the action stream), redirect the UI to a second path
So my question is, how could I watch the action stream and react to the listTodo.success() action?
UPDATE: To avoid being too specific, we can think of another case. I want to simply display an alert with window.alert() after listTodo.success() appears in the action stream. Or simply console.log(), or whatever that changes local state (instead of global redux state). Is there a way to implement that?
UPDATE 2: There is a similar question here, but for Angular w/ ngrx. What I want to do is exactly the thing described in above post, but in React / redux-observable fashion:
import { Actions } from '#ngrx/effects';
#Component(...)
class SomeComponent implements OnDestroy {
constructor(updates$: Actions) {
updates$
.ofType(PostActions.SAVE_POST_SUCCESS)
.takeUntil(this.destroyed$)
.do(() => /* hooray, success, show notification alert ect..
.subscribe();
}
}
With redux the components update based on state.
If you want to update a component based on an action than you update the state in the reducer, such as setting {...state, success: true} in the reducer. From there you simply read the state into your component as you normally would and if the state is changing to success than you show your window.
Might be a little late but I solved a similar problem by creating a little npm module. It allows you to subscribe to and listen for redux actions and executes the provided callback function as soon as the state change is complete. Usage is as follows. In your componentWillMount or componentDidMount hook:
subscribeToWatcher(this,[
{
action:"SOME_ACTION",
callback:()=>{
console.log("Callback Working");
},
onStateChange:true
},
]);
Detailed documentation can be found at https://www.npmjs.com/package/redux-action-watcher
I feel like a dialogue should be a side effect, so you'd put them in epics
I want to create an app with react and redux. My component subscribed to several states from the redux store, some of the state-data need to be prepared before the rendering can take place. Do I need to put the prepareData function into componentWillReceiveProps and write it to the state afterwards? It seems to create a lot of queries in the componentWillReceiveProps. Is there a best practice?
componentWillReceiveProps(nextProps) {
if (this.props.dataUser !== nextProps.dataUser) {
this.prepareData(nextProps.dataUser);
}
if (this.props.dataProject !== nextProps.dataProject) {
.....
}
if (this.props.dataTasks !== nextProps.dataTasks) {
.....
}
}
As Axnyff suggests, you can do your data preparation in mapStateToProps, this will trigger a render each time your redux state updates (your component can be stateless this way) :
mapStateToProps = (state) => {
const dataUserPrepared = prepareData(state.dataUser);
return { dataUser: dataUserPrepared };
}
If you have a lot of different data to prepare, which updates individually, that can be a loss in performance.
In this case you can use componentWillReceiveProps like in your question, this is fine because the setState in your prepareData() function will be batched with the received props to trigger only one render per prop update.
If you were using an app without redux then the solution would be to prepare your data before you call this.setState().
I believe the same solution applies to when using redux, your can prepare your data inside your action because you return the action object having a type and payload.
You can also prepare your data inside your reducer before returning the state object.
You could even prepare your data inside mapStateToProps of your component.
But in case you want to specific conditions under which component should re-render when state changes, then you do that in shouldComponentUpdate()
I am new to react-redux and I was surprised to see an example where a function, in this case being getVisiblieTodos, is called inside mapStateToProps. This function should be called in a reducer since it changes state? Is the code breaking "good form" for the sake of brevity? Is it okay to do this in general?
I am looking at code from this link
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
In redux we want the store to hold the minimal data needed for the app. Everything that is derived from the base data, should be computed on the fly, to prevent cloning pieces of the store, and the need to recompute all derived data when something changes in the store.
Since the visible todos list is not part of the store, but computed using the list of todos, and the visibilityFilter, the getVisibleTodos() doesn't change the store's state. It produces the derived computed data from the those two properties.
A function that is used to get data from the store, and compute derived data is known as a selector. Using selectors, the derived data is not part of the store, and computed when needed. In addition, we can use memoized selectors, to save the computation overhead.
You may see getVisibleTodos as a reducer because it includes "switch .. case" block or/and because it has 2 arguments . However, it is not a rule.
A redux reducer ( by definition) changes store state according to dispatched action , and that's why it takes two arguments ( store state + dispatched action ) and it returns new state for the store without mutation.
getVisibleTodos here is a helper function which filter an array according to string (filter).
Also , filter is not a redux-action, it is just string that decides todos to be rendered.
I may agree with you it is something weird , and if we can see the whole application (reducers, actions,... ) we can decide if it is best practices or not .
todos in this component is a calculated property based on the state of the reducer, and it is not changing any state.
It's okay to transform properties comming from recuders that are used only by one component (they are called selectors). Imagine that you use todos in other components, you will not want to make changes in one component like filtering and seeing that in the other components. If this is the case, it's fine to do it.
Also, it is a good property of your reducer to store only the needed data. More state is more complexity in the app, and more overhead to calculate new states.
It seems to me that a function should do what its name says, nothing less, nothing more.
mapStateToProps() should just do that, ie "map", and should normally not call other functions.
mapStateToProps returns an object with 2 keys, provider & plan.
The provider's value cannot be resolved until the plan is already a property of props. provider depends on the plan's property (plan.provider).
My idea was, the first time mapStateToProps gets invoked, it will return the following:
(And that is indeed what happens)
{
plan: [Object],
provider: undefined
}
Now that the plan is resolved and mapped to props I need the mapStateToProps to get invoked once again so that the provider can resolve aswell. The second time mapStateToProps should return:
{
plan: [Object],
provider: [Object]
}
Is this the right way to deal with a situation like this?
I dont think this is a good way to solve this. I believe if you have plan as a promise or an async action that you have to wait for before updating provider value, you need to do it Redux Middleware.Most reliable of them in my opinion is Thunk, which allows you to write async code in actions and then when the Promise resolves, you can update provider in your state.
To avoid undefined provider value at the UI, use defaultProps which will help you to avoid issues because of undefined provider.
I hope that helps.
Yes, that's a perfectly reasonable approach. Assuming that a second action is dispatched which adds the appropriate data to the store, mapState will re-run and return the updated values, and the component will re-render.
Your React component should be written so that it can safely handle the missing provider prop, such as checking to see if it exists and returning a "loading..." component if it doesn't.
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.