I'm using redux thunk to return an API call on an action:
export const getActiveCampaigns = () => {
return (dispatch, getState) => {
const bearer = 'Bearer ' + getState().login.bearer
return axios.get(API.path + 'campaign/active/?website_id=' + getState().selectedWebsite.selectedWebsite + '&' + API.beaconAPI_client, { headers: { 'Authorization': bearer } })
.then(function (response) {
dispatch({
type: GET_ACTIVE_CAMPAIGNS,
activeCampaigns: response.data.response
})
})
}
}
This works as in it successfully returns a list of campaigns, which I'm rendering into another component using:
class ActiveCampaignsDropdown extends Component {
// usual stuff
componentDidMount(){
this.props.dispatch(getActiveCampaigns())
}
// render function displays campaigns using this.props.activeCampaigns
}
const mapStateToProps = (state) => {
return {
activeCampaigns: state.activeCampaigns.activeCampaigns
}
}
However, note getState.selectedWebsite.selectedWebsite
on the action. This is set from an action elsewhere in the app, where a user chooses a website from a dropdown list. My reducers look like this:
export default function (state = {}, action) {
switch(action.type){
case SET_SELECTED_WEBSITE:
return {
...state,
selectedWebsite: action.websiteId
}
default:
return state;
}
}
export default function (state = {}, action) {
switch(action.type){
case GET_ACTIVE_CAMPAIGNS:
return {
...state,
activeCampaigns: action.activeCampaigns
}
default:
return state;
}
}
My action for setting the selected website:
export const setSelectedWebsite = (websiteId) => {
return {
type: SET_SELECTED_WEBSITE,
websiteId
}
}
This is combined with other reducers like so:
export default combineReducers({
login,
activeWebsites,
activeCampaigns,
selectedWebsite
})
The problem
The contents of the active campaigns dropdown box works fine on page load - and the state tree does update - but it doesn't update when the selected website changes. From what I can see:
I'm quite disappointed that Redux isn't "just working" in this instance, though it is possible I'm overlooking something silly having had only a few hours sleep! Any help appreciated.
In React, components update when one of three things happen:
In your circumstances, you're looking to update ActiveCampaignsDropdown
when state.activeCampaigns
changes in the store. To do this, you must hook up your component so that it receives this value as a prop (and thus force an update when it changes).
This can be done as follows:
import {connect} from 'react-redux'
class ActiveCampaignsDropdown extends React.Component { ... }
const mapStateToProps = (state) => ({activeCampaigns: state.activeCampaigns});
const Container = connect(mapStateToProps)(ActiveCampaignsDropdown);
export default Container;
The final Container
component will do all the work of connecting ActiveCampaignsDropdown
with the desired store state through its props.
Redux's connect()
also allows us to hook up dispatch functions for modifying data in the store. For instance:
// ... component declaration
// ... mapStateToProps
const mapDispatchToProps = (dispatch) =>
{
return {
getActiveCampaigns: () => dispatch(getActiveCampaigns())
};
}
const Container = connect(mapStateToProps, mapDispatchToProps)(ActiveCampaignsDropdown);
Once the mapping functions are defined, the container component is created, and the container is rendered, ActiveCampaignsDropdown
will be hooked up correctly. In my example, it will receive 'activeCampaigns' and 'getActiveCampaigns' as props and update accordingly when their values change.
Edit:
After taking another look at your code, I believe your issue is due to the fact that no condition has been met in order to update ActiveCampaignsDropdown
when the website has changed. By calling getActiveCampaigns()
from your WebsiteDropdown (as per your comment), this is forcing state.activeCampaigns
to change, which successfully updates ActiveCampaignsDropdown
. As mentioned in one of my comments, 'forcing' this change from a component whose responsibility isn't to do that would be considered bad practice.
A perfectly reasonable solution is for ActiveCampaignsDropdown
to 'listen' for changes to the current website and update itself accordingly. For this, you need to do two things:
(1) Map website state to the component
const mapStateToProps = (state) => {
return {
activeCampaigns: state.activeCampaigns.activeCampaigns, // unsure why structured like this
selectedWebsite: state.selectedWebsite.selectedWebsite
}
}
(2) Move your dispatch call into componentWillReceiveProps
class ActiveCampaignsDropdown extends React.Component
{
// ...
componentWillReceiveProps(nextProps)
{
if (this.props.selectedWebsite !== nextProps.selectedWebsite)
{
this.props.getActiveCampaigns();
}
}
}
Now every time the selected website changes, a refresh will occur and componentWillReceiveProps()
will be called (causing activeCampaigns
to also update). When this update has been applied, another refresh will happen and the rendered dropdown will contain the newly updated campaigns.
Some minor improvements:
If a number of your components rely on the state of the current website (which I imagine is many), then you may consider providing them with it via context
.
Now that your ActiveCampaignsDropdown
receives 'selectedWebsite' as a prop, you can pass this directly to your action function instead of having it fetch it from state (using getState()
) - which by the way should also be avoided if at all possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With