Ditlev
Ditlev

Reputation: 60

Statefull components interaction with store that might be async

I’m working on a React Flux project where async interaction is an ongoing thing and I’m still a bit wonky when it comes to the react lifecycle.

In my statefull components I have a getStateFromStores function, if there is no state in store, this returns a false bool, otherwise it returns the state.

As I have it right now the cycle is as following:

I have one bound function (ES6 react class) in my components called getState(), this simply invokes getStateFromStores and checks that the return is not false, and if not, invokes setState() with the state.

Since notifications of state change can come from different sources, I like to declare the logic in one place (getState)

State change can come from the following:

Im toying around with a state prop called fetching which I can use to control the flow, but is this problematic? – because every time I change state it triggers a render (?) — could I use static for this?

I imagine something of the following; DidMount triggers getState(), getState sets fetching to true, and now all other invoke-tries will fail because of the bool check.

Then I’d implement a resetState, that brings back the state to default (fetching false) and then getState can be triggered again.

I’d abstract all this functionality in a “Higher-Order component”

Thanks!

Upvotes: 1

Views: 137

Answers (2)

Hal Helms
Hal Helms

Reputation: 684

I take a different approach. I've found that one of the great advantages to writing React components is that you're able to think of the component as a self-contained unit -- with its own HTML, JavaScript, data-fetching -- even styles. It relies on Flux stores to keep track of data, as opposed to passing that data as props.

Suppose you have a user log in and receive back some information. Now, let's say the user wants to manage their account. We could do something like this...

<Account user_info={this.state.user_info} />

That will work fine, but can't the Account get the user_info it needs if it knows the user_id? That would lead us to...

<Account user_id={this.state.user_info.user_id}>

But, really, doesn't the store know who the logged-in user is? After all, it was integral in getting/storing that data. So why not just...

<Account />

...and let Account get the data it needs from the store -- no user_id needed, since the store already knows the logged-in user.

Anyway, just a different way to think about things.

Upvotes: 2

tobiasandersen
tobiasandersen

Reputation: 8680

What I've found working really well for me in most cases is to have a root "controller component". Let's call it Controller for now. It's basically a React component just as any other – but its responsibility differs a bit.

I see it as solely delegating. It's only responsibility is to get data (state) from the stores and pass it down (as props) to its child components and let them do all the work.

This is done by setting the data from the stores as the Controller's state. Any changes in the stores are reported back to (only) the Controller, which updates its state. Since the state is passed down as props to the children, a change in a store will trigger all child components using that data to re-render (if they have to).

I try to pass down only relevant data to the child components, but don't worry to much about them getting to much data (or about some extra re-rendering).

In practice the Controller would look something like this:

function getStateFromStores() {
    return {
        players: PlayerStore.getData(),
        cards: CardStore.getData()
    };
}

var Controller = React.createClass({
    getInitialState: function() {
        return getStateFromStores();
    },

    componentDidMount: function() {
        PlayerStore.addChangeListener(this._onChange);
        CardStore.addChangeListener(this._onChange);
    },

    componentWillUnmount: function() {
        PlayerStore.removeChangeListener(this._onChange);
        CardStore.removeChangeListener(this._onChange);
    },

    _onChange: function() {
        this.setState(getStateFromStores());
    },

    render: function() {
        return (
            <Component1 
                players={this.state.players}
                cards={this.state.cards}
            />

            <Component2
                players={this.state.players}
            />

            <Component3
                cards={this.state.players}
            />
        );
    }
});

module.exports = Controller;

Upvotes: 1

Related Questions