How to keep local component data , e.g. spinners/errors, outside of Flux stores?

488 views Asked by At

Note: I am using Reflux as my Flux library, so the samples will use its syntax. The question applies to Flux in general, however.

In my sample Flux application I have a productStore.js file that holds the state of the products in the system and listens to various product management actions, e.g REMOVE_PRODUCT, ADD_PRODUCT.

Here is a sample data in the store:

{
    products: [
        {
            productName: "A"
        },
        {
            productName: "B"
        }
    ]
}

Now I would like to add a REFRESH_PRODUCTS action to a component.

The invocation looks like that:

component.jsx

onRefresh(e) {
    actions.refreshProducts();
}

Since refreshing the products is an async operation, I would like to show the spinner while it goes on and show an error if it fails. The obvious, Flux way, would be to add the loading state and the resulting error, if such happens, to the store, like so:

productStore.js

onRefreshProducts() {

    logger.info("Logging in:", email);
    this.storeData.inProgress = true;
    this.storeData.error = null;
    this.trigger(this.data);

    api.getProducts()
        .then((response) => {
            this.storeData.products = response.data.products;
        })
        .catch((response) => {
            this.storeData.error = error;
        })
        .then(() => {
            this.storeData.inProgress = false;
            this.trigger(this.data);
        });
}

And now the store of the data becomes dirty with various flags:

{
    inProgress: false,
    error: null,

    products: [
        {
            productName: "A"
        },
        {
            productName: "B"
        }
    ]
}

This kind of state would be perfectly fine for me if multiple components would need to see the progress of products loading, or refresh failing, but in case, no other components needs that kind of information. So it feels I am putting private data to global state without a good reason.

I would like to be able to do something like that:

component.jsx - BAD CODE

onRefresh(e) {

    this.setState({error: false, inProgress: true});
    actions.refreshProducts()
        .catch(function(err) {
            this.setState({error: err});
        })
        .then(function() {
            this.setState({inProgress: false});
        });
}

Then I could keep the code of store clean. However, I have no obvious way to do that - Actions, by design, create a separation that don't allow to return data from actions.

What's the proper way to do it? How can I do private spinners/errors/etc while keeping the related data out of global state?

2

There are 2 answers

0
VitalyB On

Here is one solution I thought of while writing this question:

  • Create a new action on the store that allows to update the product data by argument, e.g: refreshProductFromData
  • Call the API directly from the component
  • Manipulate the spinners/error handling in the component
  • Pass the data retrieved from API to the store via the new action

Like so:

component.jsx

onRefresh(e) {

    this.setState({error: false, inProgress: true});
    api.getProducts()
        .then(function(data) {
            actions.refreshProductFromData(response.data.products);
        });
        .catch(function(err) {
            this.setState({error: err});
        })
        .then(function() {
            this.setState({inProgress: false});
        });
}

Not sure if it is the right/best solution or not however.

0
Yossarian21 On

I found your post because I had the same question. I think I'm going to structure mine like this, keeping everything decoupled and communicating via actions. I like to keep the component and store ignorant with regards to the API.

  • The Product Actions know how to interact with the API to complete the requested action.
  • The Product Store listens to the Completed action to update its internal state.
  • The LoadingActions manage the spinner state and are asked to show/hide the spinner when API calls are initiated.
  • I have a Spinner component that listens to LoadingActions and updates its state to show/hide the spinner.

Component:

actions.refresh();

(Product) Actions:

onRefresh: function () {
    LoadingActions.showSpinner();
    api.loadProducts().then(this.completed, this.failed).finally(LoadingActions.hideSpinner);
}

Loading Actions:

onShowSpinner: function () { ... }
onHideSpinner: function () { ... }

Store:

onRefreshCompleted: function (data) {
    this.products = data;
    this.trigger();
}