简体   繁体   中英

React-Redux: Make api call after set state

I have a page in which I recover information about some managers. When the user choose one manager using a <select> I save the id in a state. Now I would to use this id to do another fetch from another api.

My problem is that at the beginning the manager state is undefined so the second api call give me a 500 errors until the manager id is set. Once the manager is chosen, the call to the API is made continuously (because it is inserted in the render).

I would find a method to do the second api call only one time after that the manager's id is choosen, how can I do in your opinion?

componentDidMount() {
// with this part I recover the managers' list
        this.props.managerList()
    }
//...
render() {
// this is the part:
         let managers = this.props.managers
         console.log("manager: ", managers)
         let idManager = this.state.manager_id; 
         console.log("idManager: ", idManager)
         if(typeof idManager != "undefined"){
          this.props.agendaList(idManager)
         }

        return (
      <div className = "ml-5 mr-5 mt-5">
          <div>
          </div>
        <form onSubmit={this.handleSubmit}>
         <div>
          <label>Manager</label>
          <div className="bootstrap-select-wrapper">
// there I save the manager_id that I pass in my second api (agendaList)
            <select title="Choose Option" onChange={(e) => this.setState({ manager_id: e.target.value })}>
            <option value="" title="Choose Option" data-content="Cancel <span class='reset-label'></span>"></option>
            {managers && managers.length && managers.map((manager) => (
              <option key={manager.id}  value= {manager.id}  name="manager_id" >{manager.name} {manager.surname}</option>
            ))}
            </select>
            </div>
          </div>

EDIT. Ok I have edited the code thanks to your answer. The problem is now when I select the manager and do the agendaList(manager_id) call I obtain:

Before I choose manager:

(2) [{…}, {…}]

0: {id: "12", name: "Name1", surname: "Surname1"}
1: {id: "13", name: "Name2", surname: "Surname2"

After (so after I call this.props.agendaList(manager_id)

0: {id: "12", name: "Name1", surname: "Surname1"}
1: {id: "13", name: "Name2", surname: "Surname2"}
2: {manager_id: "12", date: "2020-05-27", start_at: "09:00:00"}
3: {manager_id: "12", date: "2020-05-27", start_at: "10:00:00"}

And I change the manager

(5) [{…}, {…}, {…}, {…}, {…}]
0: {id: "12", name: "Name1", surname: "Surname1"}
1: {id: "13", name: "Name2", surname: "Surname2"}
2: {manager_id: "12", date: "2020-05-27", start_at: "09:00:00"}
3: {manager_id: "12", date: "2020-05-27", start_at: "10:00:00"}
4: {manager_id: "13", date: "2020-05-28", start_at: "10:00:00"}

I would to separate the answer from my api.

case 'MANAGER_LIST':
            return [...state, ...action.managers];
        case 'AGENDA_LIST':
            return [...state, ...action.agenda];

You should not make any API call inside render method. You can write a separate handler that sets the state and makes the API call

handleChange = (e) => {
   const manager_id = e.target.value
   this.setState({ manager_id })
   this.props.agendaList(manager_id)
}
render() {
   let managers = this.props.managers
   console.log("manager: ", managers)
   let idManager = this.state.manager_id; 
   console.log("idManager: ", idManager)
   return (
      <div className = "ml-5 mr-5 mt-5">
         {/* Other code */>}
            <select title="Choose Option" onChange={this.handleChange}>
                <option value="" title="Choose Option" data-content="Cancel <span class='reset-label'></span>"></option>
                {managers && managers.length && managers.map((manager) => (
                  <option key={manager.id}  value= {manager.id}  name="manager_id" >{manager.name} {manager.surname}</option>
                ))}
            </select>
           {/*other code*/}
      </div>
   )
}

EDIT: As per your edited post it seems like you want the data to be separate. To do that you need to store them separately in reducers

const reducer = (state = {managers: [], agents: []}, action) => {
     case 'MANAGER_LIST':
            return {...state, managers: [...state.managers, ...action.managers]};
        case 'AGENDA_LIST':
            return {...state, managers: [...state.managers, ...action.agenda]};
}

post that you need to update your mapStateToProps too

const mapStateToProps = (state) => {
   return {
      managers: state.managers,
      agenda: state.agenda,
   }
}

You can wrap the second api call in an if state to only execute when manager state is not undefined. And your onChange should rather link to a function outside render, not important but good practice.

Don't set the selected ID in state. On select, you can directly call redux action. For example,

This will call be called on manager drop down select,

onMangerSelect(selectedId) {
    this.props.getManagerDetails(selectedId);
}

This wil call redux action,

const mapDispatchToProps = dispatch => {
  return {
    getManagerDetails : (managerId) => {
      dispatch(getDataFromManagerAPI(managerId));
    } 
  };
};

Well, React is about being.. reactive. A good mindset when working with React would be to always react to changes.

In my opinion, its OK to change the state like you did when selecting a value from the component (*although I'd write an handling function like the first answer suggested for the sake of a cleaner and better organized code).

What you need to do, is make sure there's a piece of code that knows how to react to that change -> in our case, making the API call. An example solution could be:

componentDidMount() {
// with this part I recover the managers' list
        this.props.managerList()
    }
componentDidUpdate(prevProps, prevState) {
        // Whenever the component updates we'll check what changed and react
        // accordingly
        this.handleNewManagerSelection(prevState);
    }
handleNewManagerSelection(prevState) {
        const { manager_id } = this.state;
        // This means we have a new manager id in our state so we need
        // to run our agendaList API call
        if (prevState.manager_id !== manager_id) {
                this.props.agendaList(manager_id);
            }
    }
render() {
// this is the part:
         let managers = this.props.managers
         console.log("manager: ", managers)
         let idManager = this.state.manager_id; 
         console.log("idManager: ", idManager)
         if(typeof idManager != "undefined"){
          this.props.agendaList(idManager)
         }

        return (
      <div className = "ml-5 mr-5 mt-5">
          <div>
          </div>
        <form onSubmit={this.handleSubmit}>
         <div>
          <label>Manager</label>
          <div className="bootstrap-select-wrapper">
// there I save the manager_id that I pass in my second api (agendaList)
            <select title="Choose Option" onChange={(e) => this.setState({ manager_id: e.target.value })}>
            <option value="" title="Choose Option" data-content="Cancel <span class='reset-label'></span>"></option>
            {managers && managers.length && managers.map((manager) => (
              <option key={manager.id}  value= {manager.id}  name="manager_id" >{manager.name} {manager.surname}</option>
            ))}
            </select>
            </div>
          </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM