Reputation: 7404
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
Upvotes: 5
Views: 5577
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
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