简体   繁体   English

componentDidMount生命周期方法中的条件异步动作不断循环

[英]conditional async actions in componentDidMount lifecycle method keeps going on a loop

I am working on a react app using redux and sagas connected to an API. 我正在使用连接到API的redux和sagas开发一个React应用程序。

There's a form component that has two dropdown fields: a Program and a Contact field. 有一个表单组件具有两个下拉字段:“程序”和“联系人”字段。 The way the form is designed to work is, when the user selects a program, the form uses the programId to fetch all the contacts that have registered for that program. 表单设计的工作方式是,当用户选择程序时,表单使用programId来获取已为该程序注册的所有联系人。 These contacts are then populated as the options for the contact dropdown field. 然后,将这些联系人填充为“联系人”下拉字段的选项。 This works and I've implemented it using the componentWillReceiveProps, like this:- 这有效,我已经使用componentWillReceiveProps实现了它,如下所示:-

componentWillReceiveProps(nextProps) {
    if (nextProps.programId !== this.props.programId) {
        this.props.fetchProgramContacts(nextProps.programId);
    }
}

Now I'm trying to have an additional feature that autopopulates the form with the programId when this form is accessed from the program's profile page. 现在,我正在尝试具有一项附加功能,当从程序的配置文件页面访问该表单时,该表单会使用programId自动填充该表单。 In this case, since the programId is preloaded into the formData even before the component mounts, the componentWillReceiveProps is not triggered as there is no change in the prop. 在这种情况下,由于即使在组件安装之前,programId仍已预加载到formData中,所以不会触发componentWillReceiveProps,因为prop中没有任何更改。 So I decided to have the programContacts fetching in the componentDidMount lifecycle method, like this:- 因此,我决定在componentDidMount生命周期方法中获取programContacts,如下所示:

componentDidMount() {
    if (this.props.programId !== '' && !this.props.programContactData.length) {
        this.props.fetchProgramContacts(this.props.programId);
    }
}

The logic is that the fetch request must be made only when the programId is not empty and the programContacts are empty. 逻辑是仅当programId不为空且programContacts为空时才必须进行获取请求。 But this goes on an endless loop of fetching. 但这是一个无休止的获取循环。

I discovered that the if statement gets executed over and over because the expressions in the body of the if statement is executed again by the componentDidMount even before the previous fetch request gets returned with the results. 我发现if语句会一遍又一遍地执行,因为if语句主体中的表达式甚至在之前的获取请求与结果一起返回之前由componentDidMount再次执行。 And because one of the conditions is to check whether the length of the results array is nonempty, the if statement returns true and so the loop goes on without letting the previous requests reach completion. 而且,由于条件之一就是检查结果数组的长度是否为非空,因此if语句返回true,因此循环继续进行而没有让先前的请求完成。

What I don't understand is why the if statement must be executed repeatedly. 我不明白的是为什么必须重复执行if语句。 Shouldn't it exit the lifecycle method once the if statement is executed once? 一旦if语句执行一次,它不应该退出生命周期方法吗?

I know that maybe it is possible to use some kind of a timeout method to get this to work, but that is not a robust enough technique for me to rely upon. 我知道也许可以使用某种超时方法来使它正常工作,但这对我来说还不够强大。

Is there a best practice to accomplish this? 是否有最佳实践来实现这一目标?

Also, is there any recommendation to not use if conditionals within the componentDidMount method? 另外,如果componentDidMount方法中有条件,是否有不建议使用的建议?

In the React lifecycle, componentDidMount() is only triggered once. 在React生命周期中, componentDidMount()仅触发一次。

Make sure the call is made from the componentDidMount and not componentWillReceiveProps . 确保调用是从componentDidMount而不是componentWillReceiveProps

If the call trully comes from componentDidMount , it means you component is recreated every time. 如果调用确实来自componentDidMount ,则意味着每次都会重新创建组件。 It can be checked by adding a console.log in the constructor of your component. 可以通过在组件的constructor中添加console.log进行检查。

In any case, you should prefer using the isFetching and didInvalidate of redux to handle data fetching / refetching. 无论如何,您应该更喜欢使用redux的isFetchingdidInvalidate来处理数据获取/重新获取。

You can see one of my detailed answer of how it works in another question: React-Redux state in the component differs from the state in the store 您可以在另一个问题中看到我的详细解答之一: 组件中的React-Redux状态与商店中的状态不同


If I focus on your usecase, you can see below an application of the isFetching and didInvalidate concept. 如果我关注您的用例,则可以在下面看到isFetchingdidInvalidate概念的应用程序。

1. Components 1.组成

Take a look at the actions and reducers but the trick with redux is to play with the isFetching and didInvalidate props. 看一下动作和isFetching ,但是redux的窍门是使用isFetchingdidInvalidate道具。

The only two questions when you want to fetch your data will be: 当您要获取数据时,仅有的两个问题是:

  1. Are my data still valid ? 我的数据仍然有效吗?
  2. Am I currently fetching data ? 我当前正在获取数据吗?

You can see below that whenever you select a program you will invalidate the fetched data in order to fetch again with the new programId as filter. 您可以在下面看到,无论何时选择程序,都将使获取的数据无效 ,以便使用新的programId作为过滤器再次获取。

Note: You should use connect of redux to pass the actions and reducers to your components of course ! 注意:当然,您应该使用redux connect来将动作和redux传递给组件!

MainView.js MainView.js

class MainView extends React.Component {
  return (
    <div>
      <ProgramDropdown />
      <ContactDropdown />
    </div>
  );
}

ProgramDropdown.js ProgramDropdown.js

class ProgramDropdown extends React.Component {
  componentDidMount() {
    if (this.props.programs.didInvalidate && !this.props.programs.isFetching) {
      this.props.actions.readPrograms();
    }
  }

  render() {
    const {
      isFetching,
      didInvalidate,
      data,
    } = this.props;

    if (isFetching || (didInvalidate && !isFetching)) {
      return <select />
    }

    return (
      <select>
        {data.map(entry => (
          <option onClick={() => this.props.actions.setProgram(entry.id)}>
            {entry.value}
          </option>
        ))}
      </select>
    );
  }
}

ContactDropdown.js ContactDropdown.js

class ContactDropdown extends React.Component {
  componentDidMount() {
    if (this.props.programs.selectedProgram &&
      this.props.contacts.didInvalidate && !this.props.contacts.isFetching) {
      this.props.actions.readContacts(this.props.programs.selectedProgram);
    }
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.programs.selectedProgram &&
      nextProps.contacts.didInvalidate && !nextProps.contacts.isFetching) {
      nextProps.actions.readContacts(nextProps.programs.selectedProgram);
    }
  }

  render() {
    const {
      isFetching,
      didInvalidate,
      data,
    } = this.props;

    if (isFetching || (didInvalidate && !isFetching)) {
      return <select />
    }

    return (
      <select>
        {data.map(entry => (
          <option onClick={() => this.props.actions.setContact(entry.id)}>
            {entry.value}
          </option>
        ))}
      </select>
    );
  }
}

2. Contact Actions 2.接触动作

I'm going to focus only on the contact actions as the program one is nearly the same. 我将仅关注接触动作,因为程序之一几乎相同。

export function readContacts(programId) {
  return (dispatch, state) => {
    dispatch({ type: 'READ_CONTACTS' });

    fetch({ }) // Insert programId in your parameter
      .then((response) => dispatch(setContacts(response.data)))
      .catch((error) => dispatch(addContactError(error)));
  };
}

export function selectContact(id) {
  return {
    type: 'SELECT_CONTACT',
    id,
  };
}

export function setContacts(data) {
  return {
    type: 'SET_CONTACTS',
    data,
  };
}

export function addContactError(error) {
  return {
    type: 'ADD_CONTACT_ERROR',
    error,
  };
}

3. Contact Reducers 3.联系异径管

import { combineReducers } from 'redux';

export default combineReducers({
  didInvalidate,
  isFetching,
  data,
  selectedItem,
  errors,
});

function didInvalidate(state = true, action) {
  switch (action.type) {
    case 'SET_PROGRAM': // !!! THIS IS THE TRICK WHEN YOU SELECT ANOTHER PROGRAM, YOU INVALIDATE THE FETCHED DATA !!!
    case 'INVALIDATE_CONTACT':
        return true;
    case 'SET_CONTACTS':
      return false;
    default:
      return state;
  }
}

function isFetching(state = false, action) {
  switch (action.type) {
    case 'READ_CONTACTS':
      return true;
    case 'SET_CONTACTS':
      return false;
    default:
      return state;
  }
}

function data(state = {}, action) {
  switch (action.type) {
    case 'SET_CONTACTS': 
      return action.data;
    default:
      return state;
  }
}

function selectedItem(state = null, action) {
  switch (action.type) {
    case 'SELECT_CONTACT': 
      return action.id;
    case 'READ_CONTACTS': 
    case 'SET_CONTACTS': 
      return null;
    default:
      return state;
  }
}

function errors(state = [], action) {
  switch (action.type) {
    case 'ADD_CONTACT_ERROR':
      return [
        ...state,
        action.error,
      ];
    case 'SET_CONTACTS':
      return state.length > 0 ? [] : state;
    default:
      return state;
  }
}

Hope it helps. 希望能帮助到你。

The actual problem is in the componentWillReceiveProps method itself, the infinite loop is created here. 实际的问题出在componentWillReceiveProps方法本身中,在这里创建了无限循环。 You are checking if current and next programId will not match, and then trigger an action that will make current and next programId not match again. 您正在检查当前和下一个programId是否不匹配,然后触发将使当前和下一个programId再次不匹配的操作。 With given action fetchProgramContacts you are somehow mutating the programId. 使用给定的fetchProgramContacts操作,您将以某种方式更改programId。 Check your reducers. 检查您的减速器。

One of the solution to this is to have reqFinished (true/false) in your reducer, and then you should do something like this: 解决此问题的方法之一是在减速器中进行reqFinished(true / false),然后您应该执行以下操作:

componentWillReceiveProps(nextProps){
  if(nextProps.reqFinished){
    this.props.fetchProgramContacts(nextProps.programId);
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 模拟componentDidMount生命周期方法进行测试 - Mock componentDidMount lifecycle method for testing “componentDidMount”生命周期方法不更新状态 - The 'componentDidMount' lifecycle method does not update the state 在React中使用componentWillMount或componentDidMount生命周期函数进行异步请求 - Use componentWillMount or componentDidMount lifecycle functions for async request in React 如何通过React componentDidMount()方法使用异步等待? - How to use async await with React componentDidMount() method? 我想测试是否在生命周期方法 componentDidMount 中调用了某个方法,但出现错误 - I want to test whether a method is called within the lifecycle method componentDidMount, but I am getting an error 使用Ref实现ComponentDidMount LifeCycle事件 - React ComponentDidMount LifeCycle Event With Ref 动作在componentDidMount内部不起作用 - actions not working inside componentDidMount ComponentDidMount 中的多个 Redux 操作 - Multiple Redux Actions in ComponentDidMount 在React JS componentDidMount生命周期方法中调用的警报在初始渲染之前而不是之后弹出 - Alert invoked in React JS componentDidMount lifecycle method pops up before the initial render instead of after 当从componentDidMount()校准异步函数时,React Native“ Reducer可能不会调度动作” - React Native “Reducers may not dispatch actions” when caling async function from componentDidMount()
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM