Await not working in useEffect with redux - reactjs

I am trying to fetch some data from the API and setting the data in my redux store. However, when I try to do some operation with the data from the redux store that variable is empty. I have used await but it seems it does not work. However, after useEffect the redux store(api data) data is visible and can do operations on it. Any help is appreciated. Please note I need to access the redux store field not just get the returned data from the function. Accessing the redux store field is important. Here is my code:
useEffect(() => {
async function loadData() {
const startingDateYear = moment();
const eventDates = generateDatesForYear(startingDateYear.year().toString());
await dispatch(fetchData('0009', eventDates[0], eventDates[1]));
}
loadData();
console.log(event.myDataArray) // event is a reducer and myDataArray is the field. It can be accessed outside the function with data incorporated but within useEffect I am not able to use the freshly fetched data.
return {};
}, []);
export const fetchData = (p1, p2, p3) => {
return async dispatch => {
const path = `dataFromAPIURL`;
try {
dispatch({
type: FETCH_STARTED,
});
const myDataArray = await RestService.get(path);
dispatch({
type: FETCH_FINISHED,
});
dispatch({
type: UPDATE_REDUCER_STATE,
payload: myDataArray,
});
return myDataArray;
} catch (error) {
// TODO: error handling
dispatch({ type: FETCH_ERROR, payload: error });
}
};
};

You need a second useEffect with dependency event.myDataArray
useEffect(() => {
async function loadData() {
const startingDateYear = moment();
const eventDates = generateDatesForYear(startingDateYear.year().toString());
await dispatch(fetchData("0009", eventDates[0], eventDates[1]));
}
loadData();
}, []);
useEffect(() => {
console.log(event.myDataArray);
}, [event.myDataArray]);

Related

How to re-run useEffect after a submit function?

Hello Guys!
So in my Project, I do a fetch data function in useeffect but when I add a new element to the firestore I want that the useEffect to run again so in the list will contain the added element, somebody can give me advice on how can I do it ?
useEffect(() => {
if (session) {
fetchTodos();
}
}, [session]);
const fetchTodos = async () => {
const fetchedtodos = [];
const querySnapshot = await getDocs(collection(db, session.user.uid));
querySnapshot.forEach((doc) => {
return fetchedtodos.push({ id: doc.id, data: doc.data() });
});
setTodos(fetchedtodos);
};
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: "active",
});
}
I want that when I run the submitHandler the useeffect run again so the list will be the newest
The only way to get a useEffect hook to run again, is to change something in the dependency array, or to not provide an array at all, and get the component to re-render (by changing props or state). See useEffect Documentation
You could just call fetchTodos directly after you call addDoc:
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: "active",
});
return fetchTodos();
}
In my experience, the best way to do what you are trying to do is to have any requests that modify your data in the backend return the difference and then modify your state accordingly:
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: 'active',
});
setTodos((prev) => [...prev, data]);
};
This way you don't have to do any large requests for what is mostly the same data within the same session.
Of course, this method is not ideal if multiple clients/users can modify your backend's data, or if you do not control what the endpoint responds with.
Hope this helps.

Axios calls with React : best practises

i want to know if there is some clean code or update to make it on my code, because i think i repeat the same code on every actions on my redux, my question is how can I avoid calling axios on my actions files ?
Please take a look on my code here :
export const SignInType = (host, lang) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_SIGNINTYPE_REQUEST,
});
const { data } = await axios.get(
`/${lang}/data?host=${host}`
);
console.log({ data });
dispatch({
type: USER_LOGIN_SIGNINTYPE_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_CLEAR_ERROR,
});
} catch (err) {
dispatch({
type: USER_LOGIN_SIGNINTYPE_FAIL,
payload: err,
});
}
};
I Really want to delete the Axios name from my actions file and make it on a separate file, but how can i do this ?
Thank you
We can suggest but there's no correct answer to this, initially any redundant lines of code can be abstracted, so in order to make things a little bit easier, we need to abstract the obvious and add the meaningful, e.g:
abstract the way you write action creators:
const actionComposer = (options) => (...args) => async dispatch => {
const modifiedDispatch = (type, payload) => dispatch({ type, payload });
const { action, onSuccess, onFailed } = options(modifiedDispatch);
try {
if (action) {
const res = await action(...args)
onSuccess(res);
}
} catch (err) {
onFailed(err)
}
}
then your code can look like this:
export const SignInType = actionComposer((dispatch)=> {
return {
action: async (host, lang) => {
dispatch(USER_LOGIN_SIGNINTYPE_REQUEST);
const { data } = await axios.get(`/${lang}/data?host=${host}`);
return data;
},
onSuccess: (res) => {
dispatch(USER_LOGIN_SIGNINTYPE_SUCCESS, data);
dispatch(USER_LOGIN_CLEAR_ERROR);
},
onFailed: (err) => {
dispatch(USER_LOGIN_CLEAR_ERROR, err.message)
}
}
})
Redux Toolkit already has a createAsyncThunk API that does all the work of defining the action types and dispatching them for you. You should use that.
Alternately, you can use our RTK Query data fetching and caching library, which will eliminate the need to write any data fetching logic yourself.

Update state with Object using React Hooks

I'm getting data from Firebase and want to update state:
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // is empty
}
);
})
} catch (error) {
console.log('xxx', error);
}
}
The setAllProfile will update the state when the iteration is done. So in order for your code to work, you will need to pass the callback function to the setAllProfile as shown in the docs
setAllProfile((prevState) => [...prevState, nProfile])
UPDATE
Example demonstrating this at work
Since setAllProfile is the asynchronous method, you can't get the updated value immediately after setAllProfile. You should get it inside useEffect with adding a allProfile dependency.
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // Old `allProfile` value will be printed, which is the initial empty array.
useEffect(() => {
console.log(allProfile);
}, [allProfile]);
UPDATE
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
const profiles = [];
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
profiles.push(nProfile);
}
);
setAllProfile([...allProfile, ...profiles]);
})
} catch (error) {
console.log('xxx', error);
}
}
You are calling setState inside a map and therefore create few async calls, all referred to by current ..allProfile value call (and not prev => [...prev...)
Try
let arr=[]
querySnapshot.docs.map(doc => {
arr.push({ id: doc.id, doc: doc.data() })
}
setAllProfile(prev=>[...prev, ...arr])
I don't sure how the architecture of fetching the posts implemented (in terms of pagination and so on, so you might don't need to destruct ...prev

React Redux - Wait until data is loaded

I'm using Redux (+ thunk) to fetch data from my API. I implemented a Data Fetcher component that calls all the actions and once done, dispatches a LOADED. In my actual main component where I render content, I wait until isLoaded flag in the props is set to true.
Here's the method in the data fetcher:
const fetchData = () => {
if (isAuthenticated) {
const p = [];
p.push(fetchDataSetOne());
p.push(fetchDataSetTwo());
Promise.all(p).then( () => setHasLoaded() );
}
}
Each of those fetch methods returns an axios promise, in which I dispatch once retrieved like so:
export const fetchDataSetOne = () => dispatch => {
return axios.get(`${API_URL}/dataSetOne`)
.then(res => {
dispatch({
type: FETCH_ALL_DATA_SET_ONE,
payload: res.data.docs
});
});
};
In my component's render function I only render the content when loaded (which is set by the LOADED dispatch from the setHasLoaded action) like so:
{ hasLoaded && <MyContent> }
Even though I "wait" for the actions to finish (= Promise.all), my variable hasLoaded is set to true before the fetched data is set. Can anybody help?
The problem is you return a function NOT a promise.
This resolves immediately.
See working code sample
export const fetchData2 = dispatch => () => {
dispatch({type: 'START_FETCH'})
const p = [
fetchDataSetOne(dispatch),
fetchDataSetTwo(dispatch)
];
Promise.all(p).then((res) => setHasLoaded(res));
};
// this returns a promise AFTER it calls an action
const fetchDataSetOne = dispatch => {
return axois.get(`${API_URL}/dataSetOne`).then(res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res.data.docs
});
});
};
This resolves after both promises are resolved, but the state updates after each promise is resolved. To update state after all promises resolve, try this:
export const fetchData3 = dispatch => () => {
dispatch({ type: "START_FETCH" });
const p = [
axois.get(`${API_URL}/dataSetOne`),
axois.get(`${API_URL}/dataSetTwo`)
];
Promise.all(p).then(callActions(dispatch));
};
const callActions = dispatch => res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res[0].data.docs
});
dispatch({
type: "FETCH_ALL_DATA_SET_TWO",
payload: res[1].data.docs
});
setHasLoaded(res);
};

Fetching data from store if exists or call API otherwise in React

Let's assume I have a component called BookOverview that displays details of a book.
I'm getting data with an action:
componentDidMount() {
this.props.getBook(areaId);
}
And then I get the data with axios:
export const getBook = () => async dispatch => {
const res = await axios.get(
`${API}/${ENDPOINT}`
);
dispatch({
type: GET_BOOK,
payload: res.data
});
};
How shall I change this code to:
if redux store already have the book loaded - return it
if no book is present in the store - call the relevant API?
What is the best practise to achieve that please?
You can have the getState inside your async action creator like this:
export const getBook = () => async (dispatch, getState) => {
if(!getState().book /* check if book not present */) {
const res = await axios.get(
`${API}/${ENDPOINT}`
);
dispatch({
type: GET_BOOK,
payload: res.data
});
} else {
dispatch({
type: GET_BOOK,
payload: getState().book
});
}
};
For More Async Actions-Redux
You can try it this way:
componentDidMount() {
if(this.props.book==null){
this.props.getBook(areaId);
}
}
I assumed that you have a property called book in your props. that populates from the particular reducer.
You have to subscribe the particular reducer to get the this.props.book - This gives the value that you have in your store.

Resources