J.K.A.
J.K.A.

Reputation: 7404

Redux + Hooks useDispatch() in useEffect calling action twice

I'm beginner in redux & hooks. I am working on form handling and trying to call an action through useDispatch hooks but it is calling my action twice.

I'm referring this article.

Here is the example:

useProfileForm.js

import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchProfile } from '../../../redux/profile/profile.actions';

const useProfileForm = (callback) => {

    const profileData = useSelector(state =>
        state.profile.items        
    );

    let data;
    if (profileData.profile) {
        data = profileData.profile;
    }

    const [values, setValues] = useState(data);

    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(fetchProfile());
    }, [dispatch]);

    const handleSubmit = (event) => {
        if (event) {
            event.preventDefault();
        }
        callback();
    };

    const handleChange = (event) => {
        event.persist();
        setValues(values => ({ ...values, [event.target.name]: event.target.value }));
    };

    return {
        handleChange,
        handleSubmit,
        values,
    }
};

export default useProfileForm;

Action

export const FETCH_PROFILE_BEGIN = "FETCH_PROFILE_BEGIN";
export const FETCH_PROFILE_SUCCESS = "FETCH_PROFILE_SUCCESS";
export const FETCH_PROFILE_FAILURE = "FETCH_PROFILE_FAILURE";
export const ADD_PROFILE_DETAILS = "ADD_PROFILE_DETAILS";

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    return response;
}

function getProfile() {
    return fetch("url")
        .then(handleErrors)
        .then(res => res.json());
}

export function fetchProfile() {
    return dispatch => {
        dispatch(fetchProfileBegin());
        return getProfile().then(json => {
            dispatch(fetchProfileSuccess(json));
            return json;
        }).catch(error =>
            dispatch(fetchProfileFailure(error))
        );
    };
}

export const fetchProfileBegin = () => ({
    type: FETCH_PROFILE_BEGIN
});

export const fetchProfileSuccess = profile => {
    return {
        type: FETCH_PROFILE_SUCCESS,
        payload: { profile }
    }
};

export const fetchProfileFailure = error => ({
    type: FETCH_PROFILE_FAILURE,
    payload: { error }
});

export const addProfileDetails = details => {
    return {
        type: ADD_PROFILE_DETAILS,
        payload: details
    }
};

Reducer:

    import { ADD_PROFILE_DETAILS, FETCH_PROFILE_BEGIN, FETCH_PROFILE_FAILURE, FETCH_PROFILE_SUCCESS } from './profile.actions';

    const INITIAL_STATE = {
        items: [],
        loading: false,
        error: null
    };

    const profileReducer = (state = INITIAL_STATE, action) => {
        switch (action.type) {
            case ADD_PROFILE_DETAILS:
                return {
                    ...state,
                    addProfileDetails: action.payload
                }
            case FETCH_PROFILE_BEGIN:
                return {
                    ...state,
                    loading: true,
                    error: null
                };

            case FETCH_PROFILE_SUCCESS:
                return {
                    ...state,
                    loading: false,
                    items: action.payload.profile
                };

            case FETCH_PROFILE_FAILURE:
                return {
                    ...state,
                    loading: false,
                    error: action.payload.error,
                    items: []
                };
            default:
                return state;
        }
    }

    export default profileReducer;


**Component:**

import React from 'react';
import { connect } from 'react-redux';
import useProfileForm from './useProfileForm';
import { addProfileDetails } from '../../../redux/profile/profile.actions';

const EducationalDetails = () => {

    const { values, handleChange, handleSubmit } = useProfileForm(submitForm);

    console.log("values", values);


    function submitForm() {
        addProfileDetails(values);
    }

    if (values) {
        if (values.error) {
            return <div>Error! {values.error.message}</div>;
        }

        if (values.loading) {
            return <div>Loading...</div>;
        }
    }

    return (
        <Card>
            ...some big html
        </Card>
    )
}

const mapDispatchToProps = dispatch => ({
    addProfileDetails: details => dispatch(details)
});

export default connect(null, mapDispatchToProps)(EducationalDetails);

Also when I'm passing data from const [values, setValues] = useState(data); useState to values then ideally I should receive that in component but I'm not getting as it is showing undefined.

const { values, handleChange, handleSubmit } = useProfileForm(submitForm);

values is undefined

enter image description here

enter image description here

Upvotes: 5

Views: 5577

Answers (2)

Muhammad Ahab
Muhammad Ahab

Reputation: 133

There are two ways to overcome this issue. I have researched this issue and find a solution to remove React.StrictMode from the root file in react like

root.render(
   <React.StrictMode>
      <App />
   </React.StrictMode>
);

to like this

root.render(<App />);

But React.StrictMode mode is good for capturing warnings and issue in code. so I have found another way to fix this issue that we have to use async call in useEffect to avoid double api call. So I used api call in useEffect like this

useEffect(()=>{
    let getData = setTimeout(() => {
        dispatch(fetchProfile());
      }, 0)
    return () => clearTimeout(getData)
  },[])

Or you can use promise to create an async call

Upvotes: 1

Shubham Khatri
Shubham Khatri

Reputation: 281922

The twice dispatch of action is probably because you have used React.StrictMode in your react hierarchy.

According to the react docs, in order to detect unexpected sideEffects, react invokes a certain functions twice such as

Functions passed to useState, useMemo, or useReducer

Now since react-redux is implemented on top of react APIs, actions are infact invoked twice

Also when I'm passing data from const [values, setValues] = useState(data); useState to values then ideally I should receive that in component but I'm not getting as it is showing undefined.

To answer this question, you must know that values is not the result coming from the response of dispatch action from reducer but a state that is updated when handleChange is called so that is supposed to remain unaffected by the action

I think you mean to expose the redux data from useProfileForm which forgot to do

const useProfileForm = (callback) => {

    const profileData = useSelector(state =>
        state.profile.items        
    );

    let data;
    if (profileData.profile) {
        data = profileData.profile;
    }

    const [values, setValues] = useState(data);

    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(fetchProfile());
    }, [dispatch]);

    const handleSubmit = (event) => {
        if (event) {
            event.preventDefault();
        }
        callback();
    };

    const handleChange = (event) => {
        event.persist();
        setValues(values => ({ ...values, [event.target.name]: event.target.value }));
    };

    return {
        handleChange,
        handleSubmit,
        values,
        data // This is the data coming from redux store on FetchProfile and needs to logged
    }
};

export default useProfileForm;

You can use the data in your component like

const { values, handleChange, handleSubmit, data } = useProfileForm(submitForm);

Upvotes: 3

Related Questions