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.
Related
I am new to Redux and I have encountered this issue and cannot seem to find a solution for it.
I have several API calls inside of thunks and they all work fine. This one however fails. It is different to the others in the sense that I am implementing a search feature that uses payload. I have tried to debug it but can't seem to find where the error happens exactly.
this is the error that I get on the screen:
these are the action, reducer and code in the search component:
and this is the logic to connect the component to the store and access state and actions:
Surely it must be something I am not fully understanding, I hope you can make some sense out of this.
You should change action.loadSearchResults.result to action.searchResults.result. I am assuming there is a key called result you are getting in your response from HomePageApi.searchResults(searchInput).
I know this because in your action you have this code:
export function searchMoviewResultsSuccess(searchResults) {
return {
type: types.SEARCH_RESULTS_SUCCESS,
searchResults: searchResults // This is the key you should get in your reducer not `loadSearchResults`
}
}
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!
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.
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.
In my app.component.html I create a custom component (contact-table component) that should get a new value (Account) to present after I update the in the store the connected user to be someone else.
app.component.html:
<contact-table [contacts]="connectedAccount$ |async"></contact-table>
app.component.ts:
export class AppComponent implements OnInit
{
private connectedAccount$: Observable<Account>;
constructor(private store:Store<any>) {
this.connectedAccount$ = this.store
.select(state=> state.connectedAccountReducer.connectedAccount);
this.connectedAccount$
.subscribe(account1=> {
//the app prints any new account that comes up. It's working here.
console.log(account1);
});
}
public ngOnInit() {
this.store.dispatch({
type: updateConnectedAccount,
payload: {
connectedAccount: ACCOUNTS[0]
}
});
}
}
The subscribtion in AppComponent class works great and fires any update that comes up.
The problem is that the async pipe in app.component.html does not send the new account to the contact-table component. I think he doesn't get notified for any updates.
I tried to see what is being sent to the contact-table component,
and I saw he only get one value in the first creation of the contact-table component and its undefined. Thats not possibole becouse my reducer function creates an empty Account object for the initial state of my app.
Is there anything you notice that I missed?
Check and make sure you are using Object.assign to create the changes to your state. It shouldn't return a mutated state. Redux/ngrx and the async pipe detect changes when the object hash changes (or at least this is my understanding). I've run into this problem when I wasn't creating a brand new object but accidentally mutating the existing state and returning it.
To quote the redux site -> http://redux.js.org/docs/basics/Reducers.html
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.
I also had a async issue with RC2 so if you aren't using RC3 or above I'd recommend upgrading.
Not saying this is it but it is the most likely candidate from my experience. Hope this helps.
Try using the json pipe just to see if the view is getting anything from that store select.
<span>{{connectedAccount$ |async | json}}</span>
You can include ngrx-store-freeze - the problem with mutable state is easy to address.
Another way of how I often debug the dispatching of Actions is to introduce an Effect for logging purpose. This helps to identify problems as well.