0

I have a React application using Flux written in Typescript. On the page are two bootstrap drop down buttons which contain possible values for the user to select in order to influence a query result set.

Using the normal Flux Actions and Stores, I invoke a couple of api calls to retrieve the values in json of the drop-down button lists which are bound to state.

I am attempting to do an initial trigger of the calculation function which needs values from both the drop-downs once the calls have completed but as the state setting is asynchronous I don't know when to trigger it?

I have tried from the state setting functions on my component but I am always told I cannot dispatch while dispatching (i.e. call my calc function via the action/store method).

How do I wait for both calls to complete before calling the function?

componentWillMount() {
    AvailabilityTimePresetsStore.addChangeListener(this._onChange_availabilityTimePresetsStore);
    AvailabilityTimePresetActions.getTimePresets();
}

_onChange_availabilityTimePresetsStore = () => {
        let timePresets = this._getStateFromAvailabilityTimePresetsStore().presets;
        // set initial value
        let currentPreset = this.state.selectedTimePreset;
        this.setState({ timePresetOptions: timePresets, selectedTimePreset: currentPreset == null ? timePresets[0] : currentPreset });
    }

_getStateFromAvailabilityTimePresetsStore() {
    // retrieve the state from the store
    return AvailabilityTimePresetsStore.getState();
}

The above is called when emitChange is fired from my TimePresetsStore. I check to see if there was no previously selected preset and set the selectedTimePreset to the first one if not (it's an initial load). At this point I want to fire my recalc() function to get the results from a query. But if i call it here or as the callback ater setState I get the dispatcher error. I also cannot tell here if the other drop-down has been populated and set in the same way and I need both to pass the selected values to the query.

RNDThoughts
  • 892
  • 3
  • 13
  • 30
  • I would wrap them into a single action, which will call the APIs needed and dispatch the corresponding actions const. after everything has been accomplished, then you can dispatch a third cons action. May you want to share some code as an example? – Facundo La Rocca Jan 03 '18 at 16:09
  • How do i call the third action - surely it has to be triggered once emitChange has fired and the corresponding drop-downs have been updating by state change. However if i fire the third action from the state change handler in the component, i get the multiple dispatch error? – RNDThoughts Jan 03 '18 at 16:29
  • Share some code so I can provide some example, it should not be a problem. BTW, I stringly recommend you to move to redux instead – Facundo La Rocca Jan 03 '18 at 16:42
  • Updated the original question. – RNDThoughts Jan 03 '18 at 17:04

1 Answers1

1

Ok, I guess the way you are implementing Flux does not seem to be the best one. Let me share an example:

//ModelActions.js

export default class ModelActions {
  static actionOne(params) {
    Dispatcher.handleViewAction({
      actionType: 'ACTION_ONE_STARTING'
    });

    fetch('uri', ...params)
      .then(resp => {
        Dispatcher.handleViewAction({
          actionType: 'ACTION_ONE_SUCCESS',
          payload: resp
        });

        //I could also dispatch more actions here, like:
        Dispatcher.handleViewAction({
          actionType: 'ACTION_TWO'
        });
      })
      .catch((error) => {
        Dispatcher.handleViewAction({
          actionType: 'ACTION_ONE_ERROR',
          payload: error.message
        });
      });
  }
}

//ModelStore.js
import { EventEmitter } from 'events';
import assign from 'object-assign';
import YourDispatcher from '../dispatcher/YourDispatcher';

let ModelStore = assign({}, EventEmitter.prototype, {
  emitChange() {
    this.emit('change');
  },

  addChangeListener(callback) {
    return this.on('change', callback);
  },

  removeChangeListener(callback) {
    this.removeListener('change', callback);
  },

  actionOneStarting() {
    return _isActionOneStarting;
  },

  actionOneSuccess() {
    return _isActionOneSuccess;
  },

  getActionOneError() {
    return _actionOneError;
  },

  getActionOnePayload() {
    return _actionOnePayload;
  }
});

ModelStore.dispatchToken = YourDispatcher.register((action) => {
  switch (action.actionType) {
    case 'ACTION_ONE_STARTING':
      _isActionOneStarting = true;
      _isActionOneSuccess = false;
      ModelStore.emitChange();
      break;

    case 'ACTION_ONE_SUCCESS':
      _isActionOneStarting = false;
      _isActionOneSuccess = true;
      _actionOnePayload = action.payload;
      ModelStore.emitChange();
      break;

    case 'ACTION_ONE_ERROR':
      _isActionOneStarting = false;
      _isActionOneSuccess = false;
      _actionOnePayload = null;
      _actionOneError = action.error
      ModelStore.emitChange();
      break;

    default:
      break;
  }
});

module.exports = ModelStore;

//YourComponent.js
import React, { Component } from 'react';

import ModelActions from '../../actions/ModelActions';
import ModelStore from '../../stores/ModelStore';

export default class YourComponent extends Component {
  componentDidMount() {
    ModelStore.addChangeListener(this._onStoreChange);
    ModelActions.actionOne();
  }

  componentWillUnmount() {
    ModelStore.removeChangeListener(this._onStoreChange);
  }

  render() {
    return (

    );
  }

  _onStoreChange() {
    this.setState({
      starting: ModelStore.actionOneStarting(),
      success: ModelStore.actionOneSuccess(),
      error: ModelStore.getActionOneError(),
      payload: ModelStore.getActionOnePayload()
    }, () => {
      //Here the component will react as soon as an action arrives
      //DOes not matter how many actions are dispatched
    });
  }
}

Summarizing, you can dispatch as many actions as you want, each store who get interested in such actions must then have a swith for catching that action, react to the change and emit the changes, the same applies for components, which must start listening all stores needed.

Now lets take a look at how using Redux it would look like:

//YourComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';

import ModelActions from '../actions/ModelActions';

export default class YourComponent extends Component {
  componentDidMount() {
    this.props.actionOne();
  }

  render() {
    return (
        //You dont need to change the state any more, just render based on props
    );
  }
}

const mapStateToProps = state => {
  return {
    starting: state.model.starting,
    success: state.model.success,
    error: state.model.error,
    payload: state.model.payload,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    actionOne: () => ModelActions.actionOne()(dispatch) //There is a tiny trick here, look for [redux-thunk](https://github.com/gaearon/redux-thunk)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(YourComponent);

//ModelActions.js

export function ModelActions(params) {
  return dispatch => {
    dispatch({type: 'ACTION_ONE_STARTING'});

    fetch('uri', ...params)
      .then(resp => {
        dispatch({type: 'ACTION_ONE_SUCCESS', payload: resp});
      })
      .catch((error) => {
        dispatch({type: 'ACTION_ONE_ERROR', payload: error.message});
      });
  }
}

//model.js
const initialState = {
    starting: false,
    success: false,
    error: null,
    payload: null,
};

export default function model(state = initialState, action = {}) {
  switch (action.type) {
    case 'ACTION_ONE_STARTING':
      return {
        ...state,
        starting: true,
        success: false
      }
    case 'ACTION_ONE_SUCCESS':
      return {
        ...state,
        starting: false,
        success: true,
        payload: action.payload
      }
    case 'ACTION_ONE_ERROR':
      return {
        starting: false,
        success: false,
        error: action.payload
      }
    default:
      return state;
  }
}

In redux, stores are replaced with reducers, components and actions are pretty similar. One of the main differences between pure Flux and redux, is that redux injects through props all information components need to render, ensuring that the entire state of the app lives on the reducers instead of living into the components.

You will find an excellent explanation about the differences between redux and pure Flux (I say pure Flux because redux is a sort un Flux implementation) here

For more information about redux, refer to the Official Site

Facundo La Rocca
  • 3,786
  • 2
  • 25
  • 47