Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux - managing preload state

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?

like image 965
Renārs Vilnis Avatar asked Nov 25 '15 11:11

Renārs Vilnis


People also ask

Should I keep all component's state in Redux store?

There is no “right” answer for this. Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state.

Can Redux state be manipulated?

Yes it can be easily manipulated.

What state should I store in Redux?

For the most part if you have a react-redux app I would avoid local state entirely unless you can trying to solve a very component specific problem. For example I would use local state if my component does something on a setInterval and so it is keeping its own timer.


2 Answers

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:

  • Reducer Composition with Arrays
  • Reducer Composition with Objects
  • Reducer Composition with combineReducers()
  • Implementing combineReducers() from Scratch
like image 194
Dan Abramov Avatar answered Oct 06 '22 09:10

Dan Abramov


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
    };
}
like image 29
David L. Walsh Avatar answered Oct 06 '22 09:10

David L. Walsh