Search code examples
reactjsreduxreact-reduxreact-hooksredux-saga

'useEffect' not run only once



Thanks everyone, especially Mr.Drew Reese. If you are newbie as me, see his answer.


I don't know why but when I console log state data if I use useEffect, it always rerender although state generalInfo not change :/ so someone can help me to fix it and explain my wrong?

I want the result which is the data will be updated when generalInfo changes.

Thanks so much!

This is my useEffect

======================== Problem in here:

  const {onGetGeneralInfo, generalInfo} = props;
  const [data, setData] = useState(generalInfo);

  useEffect(() => {
    onGetGeneralInfo();
    setData(generalInfo);
  }, [generalInfo]);

======================== fix:

 useEffect(() => {
    onGetGeneralInfo();
  }, []);

  useEffect(() => {
    setData(generalInfo);
  }, [generalInfo, setData]); 

this is mapStateToProps

const mapStateToProps = state => {
  const {general} = state;
  return {
    generalInfo: general.generalInfo,
  };
};

this is mapDispatchToProps

const mapDispatchToProps = dispatch => {
  return {
    onGetGeneralInfo: bindActionCreators(getGeneralInfo, dispatch),
  };
};

this is reducer

case GET_GENERAL_INFO_SUCCESS: {
        const {payload} = action;
        return {
          ...state,
          generalInfo: payload,
        };
      }

this is action

export function getGeneralInfo(data) {
  return {
    type: GET_GENERAL_INFO,
    payload: data,
  };
}
export function getGeneralInfoSuccess(data) {
  return {
    type: GET_GENERAL_INFO_SUCCESS,
    payload: data,
  };
}
export function getGeneralInfoFail(data) {
  return {
    type: GET_GENERAL_INFO_FAIL,
    payload: data,
  };
}

and this is saga

export function* getGeneralInfoSaga() {
  try {
    const tokenKey = yield AsyncStorage.getItem('tokenKey');
    const userId = yield AsyncStorage.getItem('userId');
    const params = {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${tokenKey}`,
      },
    };

    const response = yield call(
      fetch,
      `${API_GET_GENERAL_INFO}?id=${userId}`,
      params,
    );
    const body = yield call([response, response.json]);

    if (response.status === 200) {
      yield put(getGeneralInfoSuccess(body));
    } else {
      yield put(getGeneralInfoFail());
      throw new Error(response);
    }
  } catch (error) {
    yield put(getGeneralInfoFail());
    console.log(error);
  }
}

Solution

  • the initial state in redux and state in component is an empty array. so I want to GET data from API. and I push it to redux's state. then I useState it. I want to use useEffect because I want to update state when I PUT the data and update local state after update.

    Ok, so I've gathered that you want fetch the data when the component mounts, and then store the fetched data into local state when it is populated. For this you will want to separate out the concerns into individual effect hooks. One to dispatch the data fetch once when the component mounts, the other to "listen" for changes to the redux state to update the local state. Note that it is generally considered anti-pattern to store passed props in local state.

    const {onGetGeneralInfo, generalInfo} = props;
    const [data, setData] = useState(generalInfo);
    
    // fetch data on mount
    useEffect(() => {
      onGetGeneralInfo();
    }, []);
    
    // Update local state when `generalInfo` updates.
    useEffect(() => {
      setData(generalInfo);
    }, [generalInfo, setData]);