Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-Native/Redux dispatch firing multiple times in action

I'm making a React/Redux app. In one of my actions, dispatch is firing 6-8 times when called for no apparent reason. See addMarkersRequestAddress below in the action file for my component:

export function addMarkersSuccess(response) {
  return {
    type: 'addMarkersSuccess',
    status: 'success',
    response: response,
    receivedAt: Date.now(),
  };
}

export function addMarkersFailure(error) {
  return {
    type: 'addMarkersFailure',
    status: 'error',
    error: error,
    receivedAt: Date.now(),
  };
}

export function addMarkersRequestCoordinates(submitFormData) {


  // Why is this always returning addMarkersFailure? Is it possibly related to why it always fires multiple times?
  // Same code as in virtualFenceWalk actions
  return (dispatch) => {

    console.log('running addMarkersRequestCoordinates');
    console.log('submitFormData: ',submitFormData);

    let JSONbody = JSON.stringify(submitFormData);
    console.log('JSONbody: ',JSONbody);

    fetch('http://localhost:8080/virtualFence', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSONbody
        }).then(function(response){
          dispatch(addMarkersSuccess(response));
        }).catch(function(error) {
          dispatch(addMarkersFailure(error));
        });

  }
}

export function addMarkersRequestAddress(submitFormData) {
  return (dispatch) => {

    console.log('running addMarkersRequestAddress');
    console.log('submitFormData: ',submitFormData);

    let JSONbody = JSON.stringify(submitFormData);
    console.log('JSONbody: ',JSONbody);

    // Make a request to a backend route that gets the coordinates from the Google Maps API
    fetch('http://localhost:8080/virtualFenceAddress', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSONbody
        }).then(function(response){
          console.log('addMarkersRequestAddress success');
          console.log('response: ',response);
          dispatch(addMarkersSuccess(response));
        }).catch(function(error) {
          console.log('addMarkersRequestAddress failure');
          console.log('error: ',error);
          dispatch(addMarkersFailure(error));
        });

  }

}

When this code runs, addMarkersSuccess will fire 6-8 times. It is somehow related to dispatch specifically, because if I remove the dispatch calls and leave only the console logs, addMarkersSuccess fires once as expected and that's it. It also seems unrelated to fetch or asynchronicity since an identical outcome occurs if fetch is removed and the same thing is tried in the main body of the function.

Here is the container wrapping around the component (since I've narrowed it down to an issue with how dispatch is called, as without dispatch other parts of the action only fire once, maybe there is an issue with how dispatch is set up here?):

import React, { Component }                                             from 'react';
import PropTypes                                                        from 'prop-types';
import { StyleSheet, View, Text, TouchableOpacity, TouchableHighlight } from 'react-native';
import { bindActionCreators }                                           from 'redux';
import { connect }                                                      from 'react-redux';
import VirtualFence                                                     from '../components/VirtualFence';
import * as VirtualFenceActions                                         from '../actions/virtualFence';

const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  back: {
    margin: 10,
    fontSize: 20,
  },
});

// Map the Redux state to props
@connect(
  state => ({
    bigState: state,
    markers: state.markers,
  }),
  dispatch => bindActionCreators(VirtualFenceActions, dispatch),
)

export default class VirtualFenceContainer extends Component {

  render() {
    return (
      <View style={styles.container}>
        <VirtualFence {...this.props} />
      </View>
    );
  }
}

Here is where the action is called in the component itself:

render() {

    const {
      addMarkersRequestAddress, addMarkersSuccess, addMarkersFailure
    } = this.props;

    return (
      <View>
        <TouchableOpacity onPress={this.toggleModal}>
          <Text style={styles.bottomText}>Add markers by street address</Text>
        </TouchableOpacity>
        <Modal isVisible={this.state.isVisible}>
          <View style={{ flex: 1 }}>
            <TouchableOpacity onPress={this.toggleModal}>
              <Text style={styles.bottomText}>Hide me!</Text>
            </TouchableOpacity>
            <Form
              ref="form"
              type={Points}
              options={pointsOptions}
            />
            <Button title="Add form field" onPress={this.addFormField}></Button>
            <Button title="Delete form field" onPress={this.deleteFormField}></Button>
            <Button
              title="Submit markers"
              onPress={(argument)=>addMarkersRequestAddress(this.refs.form.getValue())}
            />
          </View>
        </Modal>
      </View>
    );
  }

While not answering my question, some other answers here and elsewhere seemed to hint that the resolution may have something to do with my configureStore.js file, so here it is:

/* eslint global-require: 0 */

import { Platform } from 'react-native';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

// Presumably I need to add the other action files here somehow? Nothing seems to change as long as one file is listed...
import * as actionCreators from './actions/activityTracker';

let composeEnhancers = compose;
if (__DEV__) {
  // Use it if Remote debugging with RNDebugger, otherwise use remote-redux-devtools
  /* eslint-disable no-underscore-dangle */
  composeEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ||
    require('remote-redux-devtools').composeWithDevTools)({
    name: Platform.OS,
    ...require('../package.json').remotedev,
    actionCreators,
  });
  /* eslint-enable no-underscore-dangle */
}

const enhancer = composeEnhancers(applyMiddleware(thunk));

// I think the problem with multiple dispatches may be in here
// See https://stackoverflow.com/questions/49734848/redux-dispatch-fires-multiple-times
export default function configureStore(initialState) {
  const store = createStore(reducer, initialState, enhancer);
  if (module.hot) {
    module.hot.accept(() => {
      store.replaceReducer(require('./reducers').default);
    });
  }
  return store;
}

Please note that I don't really know what this file is doing. I began the app using react-native-boilerplate so this file is taken from there. If changes need to be made there, it would be super appreciated if you can detail what exactly those changes do.

EDIT 1: When this post was originally written, all dispatches after the first threw errors. After some further work in other parts of the application, the additional firings all log successful now. However, the essential question (the cause of the multiple firings) remains.

EDIT 2: Added the container wrapping around the component.

like image 923
223seneca Avatar asked Jul 23 '18 13:07

223seneca


1 Answers

The cause of my problem turned out to be in the file where I call the combineReducers helper function. I did not suspect this file had anything to do with the problem, so I had not posted it. For components with multiple keys in the initial state object, I incorrectly thought I had to do an import for each key, when in fact I needed a single import for each reducer file. I imported six variables from the virtualFence reducer, and each one caused dispatch to fire.

This is the incorrect version:

import { combineReducers }       from 'redux';
import nav                       from './nav';
import virtualFence              from './virtualFence';
import latitude                  from './virtualFence';
import longitude                 from './virtualFence';
import latitudeDelta             from './virtualFence';
import longitudeDelta            from './virtualFence';
import markers                   from './virtualFence';

export default combineReducers({
  nav,
  latitude,
  longitude,
  latitudeDelta,
  longitudeDelta,
  markers,
  virtualFence,
});

And this is the correct version:

import { combineReducers }           from 'redux';
import nav                           from './nav';
import virtualFence                  from './virtualFence';

export default combineReducers({
  nav,
  virtualFence,
});
like image 178
223seneca Avatar answered Oct 19 '22 12:10

223seneca