Why is axios being called twice in my program

2019-07-23 02:52发布

问题:

I am trying to set profile state through redux. However for some reason my axios is being called twice

my database profile.js

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

// Create Schema
const ProfileSchema = new Schema({
  user: {
    type: Schema.Types.ObjectId,
    ref: "users"
  },
  preference: [
    {
      type: String
    }
  ],

  date: {
    type: Date,
    default: Date.now
  }
});

module.exports = Profile = mongoose.model("profile", ProfileSchema);

myCreatePreferences class

import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import checkboxes from "./checkboxes";
import Checkbox from "./Checkbox";
import axios from "axios";
import { Redirect } from "react-router";
import { withRouter } from "react-router-dom";
import Select from "react-select";
import { getCurrentProfile } from "../../actions/profileActions";
const options = [
  { value: "Guns", label: "Guns" },
  { value: "Gay Marriage", label: "Gay Marriage" },
  { value: "Abortion", label: "Abortion" },
  { value: "IT", label: "IT" }
];

class CreatePreferences extends Component {
  constructor() {
    super();
    this.state = {
      selectedOption: [],
      fireRedirect: false
    };
    this.onSubmit = this.onSubmit.bind(this);
  }
  onSubmit(e) {
    e.preventDefault();
    let tempArray = [];

    for (let i = 0; i < this.state.selectedOption.length; i++) {
      tempArray[i] = this.state.selectedOption[i].value;
    }
    const preference = {
      tempArray
    };
    //axios
    // .post("/api/profile/", { tempArray: tempArray })
    //.then(res => res.data)
    // .catch(err => console.log(err));
    this.props.getCurrentProfile(preference);
    this.setState({ fireRedirect: true });
  }

  handleChange = selectedOption => {
    this.setState({ selectedOption });
    console.log(`Option selected:`, selectedOption);
  };

  render() {
    const { selectedOption } = this.state;
    console.log(selectedOption.value);
    const { fireRedirect } = this.state;
    return (
      <div>
        <form onSubmit={this.onSubmit}>
          <Select
            value={selectedOption}
            isMulti
            onChange={this.handleChange}
            options={options}
          />
          <input
            type="submit"
            className="btn btn-info btn-block mt-4"
            value="Save Preferences"
          />
          {fireRedirect && <Redirect to={"/"} />}
        </form>
      </div>
    );
  }
}
CreatePreferences.propTypes = {
  profile: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
  profile: state.profile
});

export default connect(
  mapStateToProps,
  { getCurrentProfile }
)(withRouter(CreatePreferences));

my profileActionsclass

import axios from "axios";

import {
  GET_PROFILE,
  PROFILE_LOADING,
  GET_ERRORS,
  CLEAR_CURRENT_PROFILE
} from "./types";

//Get current profile

export const getCurrentProfile = preference => dispatch => {
  dispatch(setProfileLoading());
  axios
    .post("/api/profile", preference)
    .then(res =>
      dispatch({
        type: GET_PROFILE,
        payload: res.data
      })
    )
    .catch(err =>
      dispatch({
        type: GET_PROFILE,
        payload: { err }
      })
    );
};

//Profile Loading

export const setProfileLoading = () => {
  return {
    type: PROFILE_LOADING
  };
};
//Clear Profile
export const clearCurrentProfile = () => {
  return {
    type: CLEAR_CURRENT_PROFILE
  };
};

profileReducer.js

import {
  GET_PROFILE,
  PROFILE_LOADING,
  CLEAR_CURRENT_PROFILE
} from "../actions/types";

const initialState = {
  profile: null,
  profiles: null,
  loading: false
};

export default function(state = initialState, action) {
  switch (action.type) {
    case PROFILE_LOADING:
      return {
        ...state,
        loading: true
      };
    case GET_PROFILE:
      return {
        ...state,
        profile: action.payload,
        loading: false
      };
    case CLEAR_CURRENT_PROFILE:
      return {
        ...state,
        profile: null
      };
    default:
      return state;
  }
}

The index.js class redux store.

import { combineReducers } from "redux";
import authReducer from "./authReducer";
import errorReducer from "./errorReducer";
import profileReducer from "./profileReducer";
import postReducer from "./postReducer";
export default combineReducers({
  auth: authReducer,
  errors: errorReducer,
  profile: profileReducer,
  post: postReducer
});

When I posting data from the createPreference class via the profileActions through axios I am getting two axios post request. It first fills the preference as expected, however it makes another call instantly and the preference is again set to null.The console.log(of the call)

preference: Array(2), _id: "5bbc73011f67820748fcd9ab", user: "5bb87db33cb39a844f0ea46a", date: "2018-10-09T09:21:05.968Z", __v: 0}
Dashboard.js:20 {preference: null, _id: "5bbc73011f67820748fcd9ab", user: "5bb87db33cb39a844f0ea46a", date: "2018-10-09T09:21:05.968Z", __v: 0}

Any suggestions on how to fix this?

回答1:

Since I don't have access to your all of your code (and can't debug it), here's a better approach to fetching data. I've structured it closely to what you have and if you follow the working example, you should be able to eliminate the problem.

What I did:

  1. Renamed onSubmit={this.onSubmit} to a more standard declarative this.handleSubmit method
  2. Called this.setState() in the handleSubmit class method to remove selectedOption value, then in the setState callback, called getCurrentProfile(value, history) (substitute the value with your tempArray)
  3. Changed your <input type="submit" ... /> to a <button type="submit" ... />
  4. Added a return for the axios.get(...) call (I've also included an async/await version of getCurrentProfile which might be easier to understand -- also substitute the axios.get call for your axios.post call)
  5. Removed Redirect and instead placed a redirect inside the action creator as history.push('/'); (once request has been successfully sent, it'll redirect the user back to "/" -- if error, no redirect)
  6. Always keep your redux state as a 1:1. In other words, if it's an array, then it stays an array (not null), if it's a string, it stays a string (not number)...etc. When using PropTypes, your app will throw errors if you do not keep this 1:1 pattern. For example, you're initially setting profile: null, but then you set it as profile: [ Object, Object, Object ... ]. Instead, it should initially be: profile: [].
  7. When using PropTypes, avoid ambiguous types such as object or array and instead describe how they are structured.
  8. Due to the nature of redux and how you're setting your component up, you don't need to dispatch setProfileLoading. You can just update your data and the connected React component will update to reflect the new change. Dispatching two redux actions separately within a short period of time will most likely lead to component flashing (think of it as calling this.setState() twice within a second of each other -- it'll cause your component to flash).

Working example: https://codesandbox.io/s/ovjq7k7516

SelectOption.js

import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";
import Select from "react-select";
import { clearCurrentProfile, getCurrentProfile } from "../actions";

const options = [
  { value: "todos?userId=1", label: "Todos" },
  { value: "comments?postId=1", label: "Comments" },
  { value: "users?id=1", label: "Users" },
  { value: "albums?userId=1", label: "Albums" }
];

class SelectOption extends Component {
  state = {
    selectedOption: []
  };

  handleSubmit = e => {
    e.preventDefault();
    const { getCurrentProfile, history } = this.props;
    const { value } = this.state.selectedOption;

    this.setState({ selectedOption: [] }, () =>
      getCurrentProfile(value, history)
    );
  };

  handleChange = selectedOption => this.setState({ selectedOption });

  render = () => (
    <div className="container">
      <form onSubmit={this.handleSubmit}>
        <Select
          value={this.state.selectedOption}
          onChange={this.handleChange}
          options={options}
        />
        <div className="save-button">
          <button type="submit" className="uk-button uk-button-primary">
            Save Preferences
          </button>
        </div>
        <div className="clear-button">
          <button
            type="button"
            onClick={this.props.clearCurrentProfile}
            className="uk-button uk-button-danger"
          >
            Reset Preferences
          </button>
        </div>
      </form>
    </div>
  );
}

export default connect(
  state => ({ profile: state.profile }),
  { clearCurrentProfile, getCurrentProfile }
)(withRouter(SelectOption));

SelectOption.propTypes = {
  clearCurrentProfile: PropTypes.func.isRequired,
  getCurrentProfile: PropTypes.func.isRequired,
  profile: PropTypes.shape({
    profile: PropTypes.arrayOf(PropTypes.object),
    profiles: PropTypes.arrayOf(PropTypes.object),
    loading: PropTypes.bool
  }).isRequired
};

actions/index.js

import axios from "axios";
import { GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE } from "../types";

//Get current profile
export const getCurrentProfile = (preference, history) => dispatch => {
  // dispatch(setProfileLoading()); // not needed 
  return axios
    .get(`https://jsonplaceholder.typicode.com/${preference}`)
    .then(res => {
      dispatch({
        type: GET_PROFILE,
        payload: res.data
      });
      // history.push("/") // <== once data has been saved, push back to "/"
    })
    .catch(err =>
      dispatch({
        type: GET_PROFILE,
        payload: { err }
      })
    );
};

//Get current profile (async/await)
// export const getCurrentProfile = (preference, history) => async dispatch => {
//   try {
//     dispatch(setProfileLoading()); // not needed

//     const res = await axios.get(
//       `https://jsonplaceholder.typicode.com/${preference}`
//     );

//     dispatch({
//       type: GET_PROFILE,
//       payload: res.data
//     });

//     // history.push("/") // <== once data has been saved, push back to "/"
//   } catch (e) {
//     dispatch({
//       type: GET_PROFILE,
//       payload: { e }
//     });
//   }
// };

//Profile Loading
export const setProfileLoading = () => ({ type: PROFILE_LOADING });
//Clear Profile
export const clearCurrentProfile = () => ({ type: CLEAR_CURRENT_PROFILE });

reducers/index.js

import { combineReducers } from "redux";
import { CLEAR_CURRENT_PROFILE, GET_PROFILE, PROFILE_LOADING } from "../types";

const initialState = {
  profile: [],
  profiles: [],
  loading: false
};

const profileReducer = (state = initialState, { type, payload }) => {
  switch (type) {
    case PROFILE_LOADING:
      return {
        ...state,
        loading: true
      };
    case GET_PROFILE:
      return {
        ...state,
        profile: payload,
        loading: false
      };
    case CLEAR_CURRENT_PROFILE:
      return {
        ...state,
        profile: []
      };
    default:
      return state;
  }
};

export default combineReducers({
  profile: profileReducer
});