ReactJS Flux Utils components
Asked Answered
P

1

11

There is interesting article which describes 4 main classes exposed in Flux Utils.

  1. Store
  2. ReduceStore
  3. MapStore (removed from 3.0.0)
  4. Container

But it's not super clear what should be used for certain situations. There are only 2 examples for ReduceStore and Container, but no samples for others unfortunately.

Could you please explain basic usage for these 4 components: when and where they should be used in real life?

Extended answers and code examples would be really appreciated!

UPDATE:

MapStore has been removed starting from 3.0.0

Peru answered 28/1, 2016 at 20:28 Comment(2)
It looks like MapStore is no longer in the articleMesosphere
@DanEsparza thank you. Updated the question.Peru
W
23

By poking through the code and reading through the method documentation, here's what I can work out (I have not used these classes myself, as I use other Flux frameworks).

It's actually useful to go in almost reverse order for these.

Container

This is not a subclass of FluxStore because it is, unsurprisingly, not a store. The Container is a wrapper class for your React UI components that automatically pulls state from specified stores.

For example, if I have a React-driven chat app with a component that lists all my logged-in friends, I probably want to have it pull state from a LoggedInUsersStore, which would hypothetically be an array of these users.

My component would look something like this (derived from the code example they provide):

import {Component} from 'react';
import {Container} from 'flux/utils';

import {LoggedInUsersStore} from /* somewhere */;
import {UserListUI} from /* somewhere */;

class UserListContainer extends Component {
  static getStores() {
    return [UsersStore];
  }

  static calculateState(prevState) {
    return {
      loggedInUsers: LoggedInUsersStore.getState(),
    };
  }

  render() {
    return <UserListUI counter={this.state.counter} />;
  }
}

const container = Container.create(UserListContainer);

This wrapper automatically updates the component's state if its registered stores change state, and it does so efficiently by ignoring any other changes (i.e. it assumes that the component does not depend on other parts of the application state).

I believe this is a fairly direct extension of Facebook's React coding principles, in which every bit of UI lives in a high-level "Container." Hence the name.

When to use

  • If a given React component is entirely dependent on the state of a few explicit stores.
  • If it does not depend on props from above. Containers cannot accept props.

ReduceStore

A ReduceStore is a store based entirely on pure functions---functions that are deterministic on their inputs (so the same function always returns the same thing for the same input) and produce no observable side effects (so they don't affect other parts of the code).

For example, the lambda (a) => { return a * a; } is pure: it is deterministic and has no side effects. (a) => { echo a; return a; } is impure: it has a side effect (printing a). (a) => { return Math.random(); } is impure: it is nondeterministic.

The goal with a ReduceStore is simplification: by making your store is pure, you can make certain assumptions. Because the reductions are deterministic, anyone can perform the reductions at any time and get the same result, so sending a stream of actions is all but identical to sending raw data. Likewise, sending the raw data is perfectly reasonable because you were guaranteed no side effects: if my entire program is made of ReduceStores, and I overwrite the state of one client with the state of another (calling the required redraws), I am guaranteed perfect functionality. Nothing in my program can change because of the actions rather than the data.

Anyway, a ReduceStore should only implement the methods explicitly listed in its documentation. getInitialState() should determine the initial state, reduce(state, action) should transform state given action (and not use this at all: that would be non-deterministic/have side effects), and getState() & areEqual(one,two) should handle separating the raw state from the returned state (so that the user can't accidentally modify it).

For example, a counter would be a sensible ReduceStore:

class TodoStore extends ReduceStore {
    getInitialState() {
        return 0;
    }

    reduce(state, action) {
        switch(action.type) {
            case 'increment':
                return state + 1;
            case 'decrement':
                return state - 1;
            case 'reset':
                return 0;
            default:
                return state;
    }

    getState() {
        // return `this._state`, which is that one number, in a way that doesn't let the user modify it through something like `store.getState() = 5`
        // my offhand JS knowledge doens't let me answer that with certainty, but maybe:
        var a = this._state + 1;
        return a - 1;
    }
}

Notice that none of the transforms explicitly depended on the current state of the object: they only operated on the state variable they were passed. This means that an instance of store can calculate state for another instance of the same store. Not so useful in the current implementation of FB Flux, but still.

When to use

  • If you like pure-functional programming (yay!)
  • and if you don't like it enough to use a framework explicitly built with that assumption (redux, NuclearJS)
  • and you can sensibly write a store that is purely-functional (most stores can, and if they can't it might make sense to think about architecture a little more)

Note: this class does not ensure that your code is purely-functional. My guess is that it will break if you don't check that yourself.

I would always use this store. Unless I could use a...

FluxMapStore [DEPRECATED]

This class is no longer part of Flux!

This is a subclass of ReduceStore. It is for such pure-functional stores that happen to be Maps internally. Specifically, Immutable.JS maps (another FB thing!).

They have convenience methods to get keys and values from the state:

WarrantiesStore.at('extended') rather than WarrantiesStore.getState().get('extended').

When to use

  • As above, but also
  • if I can represent this store using a Map.

FluxStore

This brings us to FluxStore: the catch-all Store class and generic implementation of the Flux Store concept.

The other two stores are its descendants.

The documentation seems to me to be fairly clear on its usage, so I'll leave it at that

When to use

  • If you cannot use the other two Store util classes to hold your data
  • and you don't want to roll your own store

In my case, that would be never: I prefer immutable frameworks like redux and NuclearJS because they are easier for me to reason about. I take care to structure my stores in a purely functional way. But if you don't, this class is good.

Whiffletree answered 31/1, 2016 at 5:18 Comment(5)
awesome explanation! I have one question though. What if I have a ReduceStore that stores an array of object as its state, and there is a 'search/filter' action that comes from the dispatcher. Should I be returning the filtered state in the reduce method? The problem being that if I do so, then my state always gets smaller (due to typing more chars) but when the user empties the search string, I'll have nothing left anymore in my state. What should be done in this case?Nameless
Perhaps you would want a separate store in that case :). Or you don't use a ReduceStore, or you add another key, say, originalData and filteredData.Whiffletree
Hi, thanks for the really good explanation. I'm trying to put the pieces together for my first Flux app. I have one question. How/where does the UserListContainer in the example get instantiated since it is not being exported.Papilionaceous
It doesn't get instantiated in my example. You'd have to export all of these classes to use them.Whiffletree
I think const container = Container.create(CounterContainer); should be const container = Container.create(UserLIstContainer);. Right?Aviation

© 2022 - 2024 — McMap. All rights reserved.