16

I'm building an application, where I need to preload people and planet data (it's likely that in the future more preload requirements may be added) on launch of the application. I want to have value in the store that represents the global state of the app as loaded: <boolean>. The value would be true only then when the preload requirements people.loaded: true and planet.loaded: true are true. The store would look something like this:

Store
├── loaded: <Boolean>
├── people:
│   ├── loaded: <Boolean>
│   └── items: []
├── planets:
│   ├── loaded: <Boolean>
│   └── items: []

Separate action creators make the needed async requests and dispatch actions which are handled by the People and Planets reducers. As shown below (uses redux-thunk):

actions/index.js

import * as types from '../constants/action-types';
import {getPeople, getPlanets} from '../util/swapi';

export function loadPeople () {
  return (dispatch) => {
    return getPeople()
      .then((people) => dispatch(addPeople(people)));
  };
}

export function loadPlanets () {
  return (dispatch) => {
    return getPlanets()
      .then((planets) => dispatch(addPeople(planets)));
  };
}

export function addPeople (people) {
  return {type: types.ADD_PEOPLE, people};
}

export function addPlanets (planets) {
  return {type: types.ADD_PLANETS, planets};
}

export function initApp () {
  return (dispatch) => {
    loadPeople()(dispatch);
    loadPlanets()(dispatch);
  };
}

../util/swapi handles fetching people and planet data either from LocalStorage or making a request.

initApp() action creator calls other action creators within site.js just before rendering to DOM as shown below:

site.js

import React from 'react';
import {render} from 'react-dom';
import Root from './containers/root';
import configureStore from './store/configure-store';
import {initApp} from './actions';

const store = configureStore();

// preload data
store.dispatch(initApp());

render(
  <Root store={store} />,
  document.querySelector('#root')
);

1. What are the best practices for managing global preload state of the application in Redux?

2. Is having a global loaded state in the store necessary?

3. What would be a scalable way of checking app loaded state in multiple React components? It doesn't seem right to include People and Planet state for containers that just needs to know the global app state and doesn't handle rendering of People or Planets. Also that would be painful to manage when the global loaded state would be needed in multiple containers.


Quoting part of Dan's answer from Redux - multiple stores, why not? question.

Using reducer composition makes it easy to implement "dependent updates" a la waitFor in Flux by writing a reducer manually calling other reducers with additional information and in a specific order.

4. Does Dan by calling other reducers mean calling nested reducers?

Community
  • 1
  • 1
Renārs Vilnis
  • 1,160
  • 1
  • 10
  • 24

2 Answers2

17

First, let me correct your example.

Instead of

export function initApp () {
  return (dispatch) => {
    loadPeople()(dispatch);
    loadPlanets()(dispatch);
  };
}

you can (and should) write

export function initApp () {
  return (dispatch) => {
    dispatch(loadPeople());
    dispatch(loadPlanets());
  };
}

You don’t need to pass dispatch as an argument—thunk middleware takes care of this.
Of course technically your code is valid, but I think my suggestion reads easier.


  1. What are the best practices for managing global preload state of the application in Redux?

What you’re doing seems correct. There are no specific best practices.

  1. Is having a global loaded state in the store necessary?

No. As David notes in his answer, you’re better off storing only necessary state.

  1. What would be a scalable way of checking app loaded state in multiple React components? It doesn't seem right to include People and Planet state for containers that just needs to know the global app state and doesn't handle rendering of People or Planets. Also that would be painful to manage when the global loaded state would be needed in multiple containers.

If you’re concerned about duplication, create a “selector” function and place it alongside your reducers:

// Reducer is the default export
export default combineReducers({
  planets,
  people
});

// Selectors are named exports
export function isAllLoaded(state) {
  return state.people.loaded && state.planets.loaded;
}

Now you can import selectors from your components and use them in mapStateToProps function inside any component:

import { isAllLoaded } from '../reducers';

function mapStateToProps(state) {
  return {
    loaded: isAllLoaded(state),
    people: state.people.items,
    planets: state.planet.items
  };
}
  1. Does Dan by calling other reducers mean calling nested reducers?

Yes, reducer composition usually means calling nested reducers.
Please refer to my free Egghead tutorial videos on this topic:

Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
6

A loaded flag in your store state makes perfect sense.

However you've got too many of them. You've got state.people.loaded, state.planets.loaded as well as state.loaded. The latter is derived from the first two. Your store really shouldn't contain derived state. Either have just the first two or just the latter.

My recommendation would be to keep the first two, i.e. state.people.loaded and state.planets.loaded. Then your connected component can derive an ultimate loaded state. e.g.

function mapStateToProps(state) {
    return {
        loaded: state.people.loaded && state.planets.loaded,
        people: state.people.items,
        planets: state.planet.items
    };
}
David L. Walsh
  • 24,097
  • 10
  • 61
  • 46
  • But wouldn't maintaining `state.people.loaded && state.planets.loaded` be hard if it's used multiple times around the app? Redux examples usually show this to reduce derivable state. But I could wrap loaded check into a function that receives state and returns needed value. That would be more declarative. – Renārs Vilnis Nov 25 '15 at 12:28
  • 1
    It's certainly possible you'll want to derive the same thing multiple times across the app. This is something that Redux doesn't offer a solution to - it doesn't aim to. I'd still keep the store state minimalist. A practice I've recently adopted is to export repetitive functionality away to a `utils/` folder. – David L. Walsh Nov 25 '15 at 12:46
  • What is your thoughts about an idea where the action creator would dispatch multiple actions, both to a `loader` and a `people` or `planet` reducer after the fetching request? There would be issues, as the order of dispatcher functions matters, as you can't state for the `loader` reducer that for example `people` are loaded before dispatching an action to the `People` reducer. – Renārs Vilnis Nov 25 '15 at 13:24
  • 1
    @RenārsVilnis Please look at “shopping cart” example in Redux repo. It shows how to encapsulate such “state calculations” into functions we call “selectors”. Keep selectors alongside reducers—for example, `isAllLoaded(state)` would calculate `state.people.loaded && state.planets.loaded` and can be used throughout the app. – Dan Abramov Nov 25 '15 at 18:50