Slevin Slevin - 3 months ago 18
React JSX Question

How to create one store per instance in Redux?

Sometimes it would be useful to create one store per instance in Redux applications. The creator of Redux itself created a Gist which describes how to achieve this: https://gist.github.com/gaearon/eeee2f619620ab7b55673a4ee2bf8400

I asked this question already in the Gist but I think StackOverflow would be a better place for this question:

I wonder how to

dispatch
actions to the component's own special store? Is there a way to access the
store
-prop of the
<Provider />
for each
<SubApp />
(and their child-components)?

For example: I have some API classes that call
dispatch
after fetching data from a remote server. But since I can't import the "normal" store, what would be the best way to deal with custom stores to make them available for other classes/files/services?

Update 1



So I got it working, but I think it's a really dirty way (mind the
UGLY?
comments inside the code):

Provider:

Create a store per instance by creating the store inside the constructor:

export default class OffersGridProvider extends React.Component {
constructor(props) {
super(props)
this.store = createStore(reducers);
}

render() {
return (
<Provider store={this.store}>
<OffersGridContainer offers={this.props.offers} />
</Provider>
);
}
}


Container:

The Provider injects a
dispatch
method for this store to my
OffersGridContainer
which I can use to dispatch actions just to the store of this instance:

class OffersGridContainer extends React.Component {
componentDidMount() {

// UGLY???
const { dispatch } = this.props;

let destinationIds = [];
this.props.offers.forEach((offer) => {
offer.to.forEach((destination) => {
destinationIds.push(destination.destination);
});
});

// MORE UGLY???
destinationsApi.getDestinations(dispatch, destinationIds);
}

render() {
return (
<OffersGridLayout destinations={this.props.destinations} />
);
}
}

const mapStateToProps = function(store) {
return {
destinations: store.offersGridState.destinations
};
}

export default connect(mapStateToProps)(OffersGridContainer);


API method:

Just use the
dispatch
-method that I've passed as argument to my API method:

export function getDestinations(dispatch, ids) {
const url = $('meta[name="site-url"]').attr('content');

const filter = ids.map((id) => {
return `filter[post__in][]=${id}`;
}).join('&');

return axios.get(`${url}/wp-json/wp/v2/destinations?filter[posts_per_page]=-1&${filter}`)
.then(response => {
dispatch(getOffersGridSuccess(response.data));
return response;
});
}


Update 2



Just received a hint about
mapDispatchToProps
in the comments, so my
Container
now looks like this:

class OffersGridContainer extends React.Component {
componentDidMount() {
let destinationIds = [];

this.props.offers.forEach((offer) => {
offer.to.forEach((destination) => {
destinationIds.push(destination.destination);
});
});

this.props.getDestinations(destinationIds);
}

render() {
return (
<OffersGridLayout destinations={this.props.destinations} />
);
}
}

const mapStateToProps = function(store) {
return {
destinations: store.offersGridState.destinations
};
}

const mapDispatchToProps = function(dispatch) {
return {
getDestinations: function(ids) {
return destinationsApi.getDestinations(dispatch, ids);
}
}
}

export default connect(mapStateToProps, mapDispatchToProps)(OffersGridContainer);


Update 3 (the final answer)



Now everything works! Here's to code:

Provider:

export default class OffersGridProvider extends React.Component {
constructor(props) {
super(props)
this.store = createStore(reducers, applyMiddleware(thunk));
}

render() {
return (
<Provider store={this.store}>
<OffersGridContainer offers={this.props.offers} />
</Provider>
);
}
}


Container:

class OffersGridContainer extends React.Component {
componentDidMount() {
const destinationIds = this.props.offers.reduce((acc, offer) => {
return [...acc, ...offer.to.map(d => d.destination)];
}, []);

this.props.getDestinations(destinationIds);
}

render() {
return (
<OffersGridLayout destinations={this.props.destinations} />
);
}
}

const mapStateToProps = function(store) {
return {
destinations: store.offersGridState.destinations
};
}

const mapDispatchToProps = function(dispatch) {
return {
getDestinations: function(ids) {
return dispatch(destinationsApi.getDestinations(ids));
}
}
}

export default connect(mapStateToProps, mapDispatchToProps)(OffersGridContainer);


API method:

export function getDestinations(ids) {
return function(dispatch) {
const url = $('meta[name="site-url"]').attr('content');

const filter = ids.map((id) => {
return `filter[post__in][]=${id}`;
}).join('&');

return axios.get(`${url}/wp-json/wp/v2/destinations?filter[posts_per_page]=-1&${filter}`)
.then(response => {
return dispatch(getOffersGridSuccess(response.data));
});
}
}

Answer

Instead of making api calls in components directly, I suggest you to use redux-thunk package.

Next, you should pass mapDispatchToProps function as second argument to connect function, to inject action creator functions to component:

import { getDestinations } from '../actions';

class OffersGridContainer extends React.Component {    
  componentDidMount() {
    // Tip.
    const destinationIds = this.props.offers.reduce((acc, offer) => {
      return [...acc, ...offer.to.map(d => d.destination)];
    }, []);

    // instead `destinationsApi.getDestinations(dispatch, destinationIds)`
    // call action creator function
    this.props.getDestinations(destinationIds);
  }

  render() {
    return (
      <OffersGridLayout destinations={this.props.destinations} />
    );
  }
}

const mapStateToProps = function(store) {
  return {
    destinations: store.offersGridState.destinations
  };
}

const mapDispatchToProps = function(dispatch) {
  return {
    // this function will be available in component as   
    // `this.props.getDestinations`.
    getDestinations: function(destinationIds) {
      dispatch(getDestinations(destinationIds));
    }
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(OffersGridContainer);
Comments