I don't really know why I can't get this to work. All the evidence talks against it...This is the situation:
I have a grid of data and a search panel. When the search panel is changed the searchparams are updated and used for updating the data grid.
The thing which triggers the chain is when the user changes the search panel. In my component i handle search panel changes with this:
getPhotos(key, value) {
const change = [{ key: key, value: value},{ key: 'page', value: 1}]
this.props.dispatch(updateSearchParams(change))
console.log('payload.searchParams', this.props.searchParams);
this.props.dispatch(
getPhotos(
{ context:this.props.params.context,
searchParams: this.props.searchParams }
)
);
}
Thus two dispatch calls to action creators form the component. The problem is that the searchparams are not updated in time for the getPhotos call, so the grid is not updated accordingly.
I thought that dispatch calls were synchronous - thus one after the other. I guess that it is the round trip from the component, to the action creator, to the store and reducer which is "screwing" it up.
The first call does not involve any asynchronous calls.
What is the "right" way of doing this? Please be specific about what goes in the component, the action creator and the reducer.
Thanks
dispatch is synchronous (unless you are using some middleware like redux-thunk). But after this.props.dispatch(updateSearchParams(change))
, your component needs to be updated (a re-render) or the this.props.searchParams is still the old one.
You can write this.props.dispatch(getPhotos(...)) in componentWillReceiveProps(nextProps), so you can access the new props (nextProps)
If you are using redux-thunk and two actions updateSearchParams and getPhotos are always bind together, you can create another aggregated action creator for them.
const updateSearchParams = change => dispatch => {
// return a promise here
// or use callback style etc. whatever you prefered
}
const updateSearchParamsAndGetPhotos = (change, context) => dispatch => {
dispatch(updateSearchParams(change))
.then(res => {
dispatch(getPhotos({
context,
searchParams: res.data.searchParams
}))
})
}
So now after dispatching a single action, your component should receive the new photos.
I had it wrong from the beginning.
The searchparams should not go into the store. I can handle the in the component alone - in the state of the component.
This the simplifies and eliminates the problem I described above.
Of cause there could be a situation where the searchparams needed to be available for other components. In that case I would go for #CodinCat answer above with the thunk. It works, i managed to implement it before my realisation.
Thanks
Related
I am trying to get my head around a scenario where I am dispatching a synchronous redux action (using createAction of typesafe-actions) and soon after that making a network call that relies on updated props from the store.
Scenario:
Inside clearFilters handler function (handler function invoked on click of clear filters button), I am dispatching a synchronous action and then making a network call as below:
clearFilters = (): void => {
this.props.resetFilters(); //action dispatched
this.refreshData; //network call
};
Inside the refreshData function, my component expects updated filter props and based on it, it creates a searchCondition to be passed to the list api call as payload.
refreshData = (): void => {
const { listData, filters } = this.props; //get the filters prop
//Expected filters to be updated from the store post dispatch of action
const SearchCondition: SearchCondition = createSearchConditions(filters);
listData({
SearchCondition,
MaxResults: this.maxRecordsCount,
SortFields: this.getSortFields(),
}),
);
};
My component is subscribed to the filters prop using mapStateToProps:
const mapStateToProps = (state: RootState) => ({
filters: state.common.filter.filters,
});
Given that is the state of the problem I am facing, I tried to debug what happens by placing debug points in the code:
When the action is dispatched (inside clearFilters function)
Inside the reducer, where updated state is returned.
When the network call is invoked (inside clearFilters function)
In the refreshData call.
After reducer returns updated state, as per the debugging knowledge, store did not send the updated props right away. Rather, the control goes back to the next line i.e. this.refreshData() which make network call with old filters data. Only after the clearFilters function call finishes, in my componentDidUpdate, i can see that props update happen.
Does that signifies redux state change back to the store and eventually subscribed prop updates happen in an ASYNC way? If so, how does it happen? Does store sending the updated props executes in the main thread?
Any pointers/documentation would be really helpful.
The dispatch is synchronous, and the queueing of the React updates is synchronous. However, React will not re-render that component until after this whole event processing is completed, and this.props will not be updated until after that render happens. So, no, you cannot access this.props right after dispatching an action and expect that it has been updated. That will never be true.
I would suggest reading these posts that go into extensive detail on both React and React-Redux:
A (Mostly) Complete Guide to React Rendering Behavior
The History and Implementation of React-Redux
I'm using React Native and Redux to create an app that helps users find nearby fast-foods. I've got two components : Map and Settings.
Each component is connected (via react-redux) to its respective piece of state. Map can also dispatch an action creator called apiCall :
Map Component
...
connect(
({ map }) => ({ map }),
{ apiCall }
)(Map)
Settings Component
...
connect(
({ settings }) => ({ settings })
)(Settings)
I would like the action creator apiCall to read values from both map and settings pieces of state : this.props.apiCall(map, settings).
However, I want to avoid connecting my Map component to state.settings because it would re-render each time state.settings updates.
I'm still quite confused and have not found "the right approach" in solving this issue. These are the things I tried :
Connecting Map to state.settings and using shouldComponentUpdate() to prevent useless re-renders
Using getState() from the action creator to read state.settings value
Encapsulating everything in another higher component and then passing down specific props
The first two worked but seemed a bit anti-pattern and the third one was still triggering re-renders. Not quite sure why even though it felt like a good solution. I have not tried selectors yet but it seems to be another alternative.
To sum up, my question is :
How to dispatch an action that needs to read values from different pieces of state while avoiding unnecessary re-renders ?
You should use redux-thunk. That way you can return a function (a thunk) in your action creator, which will have access to state
const apiCall = (apiCallArgs) => {
return (dispatch, getState) => {
let mapState = getState('map');
let settingsState = getState('settings');
return dispatch(yourApiAction);
}
}
I have a react/redux app which has a recharts chart which animates when data is changed.
I'm using Redux and most of my actions only change a single state property which results in a single props pass. However, some of my actions are now using thunks for some async actions and calling other actions.
For example, I might have an action getChartData which would be called when the user selects an axis.
export let getChartData = axis => dispatch => {
// trimmed for brevity
fetchJSON(url).then(data => {
dispatch(dataRetrievalSuccess(data));
dispatch(updateSelectedAxis(axis));
}).catch(error => {
dispatch(dataRetrievalError(error));
});
};
In this example the updateSelectedAxis value will change a local state property responsible for displaying the currently selected axis and the dataRetrievalSuccess function would be responsible for passing props.data to the chart.
The problem I'm trying to solve is to prevent the chart from updating when the selectedAxis props of the component change but the data hasn't.
I thought I would be able to use something like componentWillRecieveProps but the issue I have here with my above thunk example is that I get one call to componentWillRecieveProps when I call dataRetrievalSuccess which has the same data in both this.props.data and nextProps.data so I can prevent the update. However when I subsequently call updateSelectedAxis I don't have the data as part of the props as it's already changed, so I can't perform logic operations based on the two values.
I thought this was possibly an ordering issue, but even if I pack this into a single action I still get multiple setting of props.
Would I solve this issue by packaging up the data and the change of axis into a single object?
I'm not quite sure the best way to go about this architecturally and would welcome any suggestions.
EDIT:
Just to expand a little, I am dispatching two actions, both which change their own bit of state which causes two renders.
I've tried writing something like this:
shouldComponentUpdate(nextProps, nextState) {
if(this.dataHasChanged(nextProps)) {
return true;
}
return false;
}
Which almost works, but each time the data the chart shows is one render behind where it needs to be.
You can access the current State of store under action creator using thunk (as thunk inject the state for you.) , then compare ajax response data with previous state data to dispatch new action.
export let getChartData = axis => (getState, dispatch) => {
// trimmed for brevity
fetchJSON(url).then(data => {
if(getState().data !== data){
dispatch(dataRetrievalSuccess(data));
dispatch(updateSelectedAxis(axis));
}
}).catch(error => {
dispatch(dataRetrievalError(error));
});
};
In my project I have action creator that depend on values that are in the state of the application to generate a new value or to decide what action to dispatch. My question is to know which is the right way to do it. I thought of two ways. Access those values within the action creator:
export const changePreviousPage = () => {
return (dispatch, getState) => {
let pagination = getState().appReducers.availability.pagination;
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
dispatch({
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
});
}
};
The other option I thought was to pass the value from the component to the action creator:
In my component
class Pagination extends Component {
...
handlePreviousPage() {
const {pagination} = this.props;
this.props.changePreviousPage(pagination);
}
...
}
In my action creator
export const changePreviousPage = pagination => {
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
return{
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
}
};
What is the best way to address it ?
In my opinion always use/retrieve the state at the closest time to execution, here the action creator (or rather more specifically the thunk you are returning that would then execute).
Remember that dispatch may have any number of middleware running before the actual store.dispatch call. This can include async middleware, so the state may have changed in between calling the dispatch and the store.dispatch call it will ultimately run.
Another one to consider is you may be dispatching multiple things in an action creator which change the state and invalidate what you passed into the action creator at the top. Also a reason why I consider let state = getState() at the top of an action creator a bad idea unless you are very sure nothing is going to change during your processing (as soon as you involve any API calls I would always use getState() again instead of using a stored variable).
Also putting data from state into props (using a redux container and connect helper method) will cause a rerender every time this changes, which could have a performance impact in some cases.
My personal coding preference is also to keep things as simple as possible in mapDispatchToProps (assuming that is where you're passing in your handlers like handlePreviousPage) and avoid any data processing (in your example it's not much, but you can easily see how that may get out of hand if you're preparing data for your action creator).
I am using react-router and redux in my latest app and I'm facing a couple of issues relating to state changes required based on the current url params and queries.
Basically I have a component that needs to update it's state every time the url changes. State is being passed in through props by redux with the decorator like so
#connect(state => ({
campaigngroups: state.jobresults.campaigngroups,
error: state.jobresults.error,
loading: state.jobresults.loading
}))
At the moment I am using the componentWillReceiveProps lifecycle method to respond to the url changes coming from react-router since react-router will pass new props to the handler when the url changes in this.props.params and this.props.query - the main issue with this approach is that I am firing an action in this method to update the state - which then goes and passes new props the component which will trigger the same lifecycle method again - so basically creating an endless loop, currently I am setting a state variable to stop this from happening.
componentWillReceiveProps(nextProps) {
if (this.state.shouldupdate) {
let { slug } = nextProps.params;
let { citizenships, discipline, workright, location } = nextProps.query;
const params = { slug, discipline, workright, location };
let filters = this._getFilters(params);
// set the state accroding to the filters in the url
this._setState(params);
// trigger the action to refill the stores
this.actions.loadCampaignGroups(filters);
}
}
Is there a standard approach to trigger actions base on route transitions OR can I have the state of the store directly connected to the state of the component instead of passing it in through props? I have tried to use willTransitionTo static method but I don't have access to the this.props.dispatch there.
Alright I eventually found an answer on the redux's github page so will post it here. Hope it saves somebody some pain.
#deowk There are two parts to this problem, I'd say. The first is that componentWillReceiveProps() is not an ideal way for responding to state changes — mostly because it forces you to think imperatively, instead of reactively like we do with Redux. The solution is to store your current router information (location, params, query) inside your store. Then all your state is in the same place, and you can subscribe to it using the same Redux API as the rest of your data.
The trick is to create an action type that fires whenever the router location changes. This is easy in the upcoming 1.0 version of React Router:
// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));
Now your store state will always be in sync with the router state. That fixes the need to manually react to query param changes and setState() in your component above — just use Redux's Connector.
<Connector select={state => ({ filter: getFilters(store.router.params) })} />
The second part of the problem is you need a way to react to Redux state changes outside of the view layer, say to fire an action in response to a route change. You can continue to use componentWillReceiveProps for simple cases like the one you describe, if you wish.
For anything more complicated, though, I recommending using RxJS if you're open to it. This is exactly what observables are designed for — reactive data flow.
To do this in Redux, first create an observable sequence of store states. You can do this using rx's observableFromStore().
EDIT AS SUGGESTED BY CNP
import { Observable } from 'rx'
function observableFromStore(store) {
return Observable.create(observer =>
store.subscribe(() => observer.onNext(store.getState()))
)
}
Then it's just a matter of using observable operators to subscribe to specific state changes. Here's an example of re-directing from a login page after a successful login:
const didLogin$ = state$
.distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
.filter(state => state.loggedIn && state.router.path === '/login');
didLogin$.subscribe({
router.transitionTo('/success');
});
This implementation is much simpler than the same functionality using imperative patterns like componentDidReceiveProps().
As mentioned before, the solution has two parts:
1) Link the routing information to the state
For that, all you have to do is to setup react-router-redux. Follow the instructions and you'll be fine.
After everything is set, you should have a routing state, like this:
2) Observe routing changes and trigger your actions
Somewhere in your code you should have something like this now:
// find this piece of code
export default function configureStore(initialState) {
// the logic for configuring your store goes here
let store = createStore(...);
// we need to bind the observer to the store <<here>>
}
What you want to do is to observe changes in the store, so you can dispatch actions when something changes.
As #deowk mentioned, you can use rx, or you can write your own observer:
reduxStoreObserver.js
var currentValue;
/**
* Observes changes in the Redux store and calls onChange when the state changes
* #param store The Redux store
* #param selector A function that should return what you are observing. Example: (state) => state.routing.locationBeforeTransitions;
* #param onChange A function called when the observable state changed. Params are store, previousValue and currentValue
*/
export default function observe(store, selector, onChange) {
if (!store) throw Error('\'store\' should be truthy');
if (!selector) throw Error('\'selector\' should be truthy');
store.subscribe(() => {
let previousValue = currentValue;
try {
currentValue = selector(store.getState());
}
catch(ex) {
// the selector could not get the value. Maybe because of a null reference. Let's assume undefined
currentValue = undefined;
}
if (previousValue !== currentValue) {
onChange(store, previousValue, currentValue);
}
});
}
Now, all you have to do is to use the reduxStoreObserver.js we just wrote to observe changes:
import observe from './reduxStoreObserver.js';
export default function configureStore(initialState) {
// the logic for configuring your store goes here
let store = createStore(...);
observe(store,
//if THIS changes, we the CALLBACK will be called
state => state.routing.locationBeforeTransitions.search,
(store, previousValue, currentValue) => console.log('Some property changed from ', previousValue, 'to', currentValue)
);
}
The above code makes our function to be called every time locationBeforeTransitions.search changes in the state (as a result of the user navigating). If you want, you can observe que query string and so forth.
If you want to trigger an action as a result of routing changes, all you have to do is store.dispatch(yourAction) inside the handler.