Philipp Philipp - 3 months ago 10
Javascript Question

Add logic to the store?

I have a redux application with a "campaign" reducer/store.

Currently I have repeated code to check if a specific campaign is loaded or needs an API call to fetch details from the DB. Much simplified it looks like this:

// Reducer ----------
export default campaignReducer => (state, action) {
const campaignList = action.payload
return {
items: {... campaignList}
}
}

// Component ----------
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};

return {
needFetch: campaign.id
&& campaign.meta
&& (campaign.meta.loaded || campaign.meta.loading),
campaign,
};
}

export default connect(mapStateToProps)(TheComponent);


Now I don't like to repeat the complex condition for
needFetch
. I also don't like to have this complex code in the mapStateToProps function at all, I want to have a simple check. So I came up with this solution:

// Reducer NEW ----------
const needFetch = (items) => (id) => { // <-- Added this function.
if (!items[id]) return true;
if (!items[id].meta) return true;
if (!items[id].meta.loaded && !items[id].meta.loading) return true;
return false;
}
export default campaignReducer => (state, action) {
const campaignList = action.payload
return {
needFetch: needFetch(campaignList), // <-- Added public access to the new function.
items: {... campaignList}
}
}

// Component NEW ----------
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};

return {
needFetch: state.campaign.needFetch(campaignId), // <-- Much simpler!
campaign,
};
}

export default connect(mapStateToProps)(TheComponent);


Question: Is this a good solution, or does the redux-structure expect a different pattern to solve this?

Question 2: Should we add getter methods to the store, like
store.campaign.getItem(myId)
to add sanitation (make sure myId exists and is loaded, ..) or is there a different approach for this in redux?

Answer

Your approach is somewhat against the idiomatic understanding of state in redux. You should keep only serializable data in the state, not functions. Otherwise you loose many of the benefits of redux, e.g. that you can very easily stash your application's state into the local storage or hydrate it from the server to resume previous sessions.


Instead, I would extract the condition into a separate library file and import it into the container component where necessary:

// needsFetch.js
export default function needsFetch(campaign) {
  return campaign.id
           && campaign.meta 
           && (campaign.meta.loaded || campaign.meta.loading);
}

// Component ----------
import needsFetch from './needsFetch';

const mapStateToProps = (state, ownProps) => {
    const campaignId = ownProps.params.campaignId;
    const campaign = state.campaign.items[campaignId] || {};

    return {
        needFetch: needsFetch(campaign),
        campaign,
    };
}

export default connect(mapStateToProps)(TheComponent);