React + Flux: Getting initial state into a store
Asked Answered
R

3

13

We have recently switched over to React + Flux from Angular to build a rather complex business application.

Taking the approach of having one container component that passes all state as properties down the component tree isn't a practical way to develop the app for us as the app makes use of large page-like modals. Enough state does get passed down to the modals for them to load their data into their stores.

The problem I have is I need to get some initial state (passed down as props) into the modal component's store. In this post the good guys over at Facebook say it's ok to use props for initial state when synchronization is not the goal.

This is how I get the initial state into my store currently:

var ABC = React.createClass({
  ...  
  getInitialState: function() {
    return ABCStore.getInitialABCState(this.props.initialA);
  },
  ...

var ABCStore = Reflux.createStore({
  ...
  init: function() {
    _state = {
      a: null,
      b: 'B init',
      c: 'C init'
    };
  },

  getInitialABCState: function(initialA) {
    _state.a = initialA;
    return _state;
  },

  getABCState: function() {
    return _state;
  }
  ...

I am unsure what the best practice is to do this, or whether this is a Flux anti-pattern?

Reveal answered 10/3, 2015 at 7:35 Comment(2)
It feels like you are on the edge of anti-pattern with that store. The great thing about Flux though, is that is more of an architectural concept than just a component. This means that you can create stores in several ways and keep it in the Flux "spirit. In a project I worked on we only used the dispatcher from the Flux library, which meant that the store wasn't initialized like your example. The store just populated itself when data was needed (actions from components or router). I do not think your example is bad practice, but it opens up a great discussion.Enameling
Yeah I'm hoping to spark some discussion around this, because I also got this feeling, but I need more concrete stuff :)Reveal
H
12

It feels wrong to me that you are using getInitialState() to change the state of your store. You should at least be doing that in componentWillMount.

I would trigger an action in componentWillMount and have the store handler update the internal state of the store (this should always be the case in flux). Then your component's change handler for the store can consume whatever data that you are currently calling your "initial state"

Hustler answered 10/3, 2015 at 19:25 Comment(2)
Adding to your answer I suggest spending some hours studying facebook's chat app. It covers almost everything.Clippers
It looks like that chat app gets the initial state in getInitialState, as opposed to the suggested componentWillMount, though.Zuniga
K
4

Reflux.listenTo does this when you provide a third argument and Reflux.connect mixin factory (that uses Reflux.listenTo under the hood) handles this for you automatically. Here is an example:

var Actions = Reflux.createActions({"doIt"});

var Store = Reflux.createStore({
    listenables: [Actions],
    init: function() {
        this.state = "I like to";
    },
    onDoIt: function() {
        this.state = "Do it";
        this.trigger(this.state);
    },
    getInitialState: function() {
        return this.state;
    }
});

var DoItButton = React.createClass({
    mixins: [Reflux.connect(Store, "label")],
    onClick: function() {
        Actions.doIt();
    },
    render: function() {
        return (<div onClick={this.onClick}>{this.state.label}</div>);
    }
});
Katleen answered 11/3, 2015 at 10:46 Comment(5)
Thanks for the answer - I'm a little confused. In your example you don't seem to send initial state to the store, but rather you are getting it from the store? In my example I need to get the property 'initialA' provided to my component into my store. Am I missing something?Reveal
@MarkusCoetzee so how come you're setting initialA from the component and not from the store?Katleen
The component receives this property (passed down from the parent container). This property forms part of the component's initial state. My question is around what is the Flux way of getting this initial state into the storeReveal
Ok, I'd pull up the store to the parent component instead. Do the initial state handling there and pass the data down by props.Katleen
I believe what @Katleen is referring to is creating a parent component that just does the data fetching part, and conveying of the props to components inside it. It's sort of a convention that has arisen that even Facebook does internally. It's a not a "Fluxy" thing or anything like that, and it's not an anti-pattern either. Kosher React. Check out an article that Michael Chan wrote about containers: medium.com/@learnreact/container-components-c0e67432e005Berky
A
0

Like other posters have said, the best thing to do is to trigger an action in componentWillMount. In ES6, this is usually done with a constructor.

Below is an example on how to do this with ES6:

(note that AuthorActions.initAuthors() is dependent on implementation, this is just an example. This could get initial state from database. Most importantly though, this action should dispatch the payload with the initial state to the dispatcher)

var _authors = [];
var AuthorStoreInstance;

class AuthorStore extends EventEmitter {

    constructor(props) {
        super(props);
    }

    init() {
        AuthorActions.initAuthors();
        this.emitChange();
    }

    addChangeListener(cb) {
        this.on(CHANGE_EVENT, cb);
    }

    removeChangeListener(cb) {
        this.removeListener(CHANGE_EVENT, cb);
    }

    emitChange() {
        this.emit(CHANGE_EVENT);
    }

    getAllAuthors() {
        return _authors;
    }

    addAuthor(author) {
        _authors.push(author);
        this.emitChange();
    }

    setAuthors(authors) {
        _authors = authors;
    }

};


AuthorStoreInstance = new AuthorStore();

Dispatcher.register((action) => {
    switch(action.actionType) {
        case ActionTypes.CREATE_AUTHOR:
            AuthorStoreInstance.addAuthor(action.author);
            break;
        case ActionTypes.INITIALIZE:
            AuthorStoreInstance.setAuthors(action.initialData.authors);
            break;

        default:
            //do nothing
    }
});

AuthorStoreInstance.init();

export default AuthorStoreInstance;

Notice how the init function is not a part of the constructor. This is because when the authorStore is constructed, the callback for the AuthorActions.initAuthors has not been registered with the dispatcher.

Initialization should happen after callbacks have been registered with the dispatcher.

edit: For clarity, initAuthors could look something like this:

initAuthors() {
    var authors = AuthorAPI.getAllAuthors();

    Dispatcher.dispatch({
        actionType: ActionTypes.INITIALIZE,
        initialData: {
            authors: authors
        }
    });
}
Aminoplast answered 11/11, 2017 at 19:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.