Redux dispatch in useEffect isn't syncronous - reactjs

I want to navigate to App screen or Auth screen, depending on the isUser prop after fetching it from the server and updating the redux store.
My first component AuthLoading.js which looks like this:
const AuthLoading = (props) => {
const isUser = useSelector((state) => state.authReducer.isUserExists);
const dispatch = useDispatch();
const fetchData = async () => {
const token = await TokensHandler.getTokenFromDevice();
dispatch(isTokenExists(token));
props.navigation.navigate(isUser ? "App" : "Auth");
};
useEffect(() => {
fetchData();
}, []);
My authActions.js looks like this:
export const isTokenExists = (token) => {
return (dispatch) => {
return HttpClient.get(ApiConfig.IDENTITY_PORT, "api/identity", {
userId: token,
}).then((response) => {
console.log(response);
dispatch({
type: IS_USER_EXISTS,
payload: response,
});
});
};
};
My authReducer.js looks like this:
const authReducer = (state = initialState, action) => {
switch (action.type) {
case IS_USER_EXISTS:
return {
...state,
isUserExists: action.payload,
};
default:
return state;
}
};
And the store:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
const configureStore = () => {
return createStore(rootReducer, applyMiddleware(thunk));
};
export default configureStore;
Unfortunately, the code inside AuthLoading.js isn't asynchronous, and isn't waiting for the updated isUser value and running the next line without it.
I've tried to .then after dispatch which still doesn't work.
I've tried changing async states and still couldn't find the problem.
I have no idea how to fix this.
Thanks for the help.

You can use another useEffect hook that runs when isUser changes and navigate inside it.
This useEffect runs once the component mounts and every time isUser changes, so someCondition can be anything that determines whether the navigation should happen or not.
useEffect(() => {
if(someCondition) {
props.navigation.navigate(isUser ? "App" : "Auth");
}
}, [isUser]); // add all required dependencies

Related

Redux - API is being called multiple times (Redux Thunk)

I am using Next.js and Redux as a state management. Everything is working perfectly fine except one thing and that is API calls. What I mean by this is that API is being called multiple times even though I dispatched it just once. When I go and see in the network tab in Google Chrome, I see multiple calls being called.
I am also using Redux Thunk and Redux Toolkit:
store
import { configureStore } from "#reduxjs/toolkit";
import layoutSlice from "./layoutSlice";
export const store = configureStore({
reducer: {
layout: layoutSlice,
},
});
layoutSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const BASE_URL = "http://localhost:1337";
export const getHeaderData = createAsyncThunk(
"layout/getHeaderData",
async () => {
const response = await axios.get(
`${BASE_URL}/api/navigations?populate=*&sort=id`
);
return response.data;
}
);
export const getFooterData = createAsyncThunk(
"layout/getFooterData",
async () => {
const response = await axios.get(
`${BASE_URL}/api/footers?populate[ContactForm][populate]=*&populate[Links][populate]=*&populate[Info][populate]=*`
);
return response.data;
}
);
const initialState = {
header: [],
footer: [],
isLoadingHeader: false,
isLoadingFooter: false,
};
const layoutSlice = createSlice({
name: "layout",
initialState,
extraReducers: {
[getHeaderData.pending]: (state) => {
state.isLoadingHeader = true;
},
[getHeaderData.fulfilled]: (state, action) => {
state.header = action.payload;
state.isLoadingHeader = false;
},
[getHeaderData.rejected]: (state) => {
state.isLoadingHeader = false;
},
[getFooterData.pending]: (state) => {
state.isLoadingFooter = true;
},
[getFooterData.fulfilled]: (state, action) => {
state.footer = action.payload;
state.isLoadingFooter = false;
},
[getFooterData.rejected]: (state) => {
state.isLoadingFooter = false;
},
},
});
export default layoutSlice.reducer;
generalLayout (where the API is called)
import React, { useEffect, useState } from "react";
import { Header, Footer } from "../components";
import { useDispatch, useSelector } from "react-redux";
import { getHeaderData, getFooterData } from "../redux/layoutSlice";
const GeneralLayout = ({ children }) => {
const { isLoadingHeader, isLoadingFooter } = useSelector(
(state) => state.layout
);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getHeaderData());
dispatch(getFooterData());
}, []);
if (isLoadingHeader === true || isLoadingFooter === true) {
return <div>Loading...</div>;
}
return (
<>
<Header />
{children}
<Footer />
</>
);
};
export default GeneralLayout;
I am also using Strapi (dont mind the query for the API call, it works for me so I do not think the problem is there, at least it should not be)
Network tab
It is because of useEffect
In development, React strictmode calls all effects twice to catch any memory leaks and other issues.
This only applies to development mode, production behavior is unchanged
So you don't want to worry about it being called twice in production/build
From official React docs (beta at time of writing)
If your Effect fetches something, the cleanup function should either abort the fetch or ignore its result:
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);
Read more here
You must add a dispatch to dependency of useEffect function..
make sure the peomise function receives parameter as the informations you want to get or post.. And call the parameter inside axios func after APIurl.

How to use async React-Redux state properly

I fetched some data from my api by react-redux. My problem is that, since it is async I have to wait for the state to update its inital value in order to use them in the app. For example I have to use
products && products.length && products[n].img
syntax not to get undefined error when I try to access the fetched data. But when I use them at the first render just as
products[n].img
the app gives undefined as it should because redux fetches the data asynchronously. How can I bypass these steps so that I can use my desired state immediately?
React code
import React, { useEffect } from "react";
import {useDispatch, useSelector} from 'react-redux'
import { listPoduct } from "../actions/productActions";
const Examples = () => {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const {loading, error, products} = productList
useEffect(()=>{
dispatch(listPoduct())
},[dispatch])
console.log(products && products.length && products[0].img)
return(
<div>
...
</div>
)
}
export default Examples
Action
export function listPoduct() {
return (dispatch) => {
const baseUrl = "/api/images"
fetch(`${baseUrl}`)
.then(res => res.json())
.then(res => {
dispatch({
type: PRODUCT_LIST_SUCCESS,
payload: res
})
})
}
}
Reducer
export const productListReducer = (state = { products: [] }, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return {loading:true, products:[]}
case PRODUCT_LIST_SUCCESS:
return {loading:false, products: action.payload}
case PRODUCT_LIST_FAIL:
return {loading:false, error: action.payload}
default:
return state
}
}
Store
import {createStore, combineReducers, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import {productListReducer, productDetailsReducer} from './reducers/productReducer'
const reducer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer
})
const initialState = {}
const middleware = [thunk]
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
export default store
The short answer is that you cannot. Sadly.
Your request is asynchronous, so there's just no data available immediately.
In your particular case, my advice would be to render some kind of spinner-loader conditionally (if loading is set to true) and only the loader.
In this case, if you have loading set to true, you will not reach the place where you can actually read the data (you will render-and-return before). And once loading switches back to false, you can now display the data safely as the request is finished and the data is in the right place.
The same applies to the failure state (as there's also no data available if the request failed).
Here's your modified code (as an example):
const Examples = () => {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const {loading, error, products} = productList
useEffect(()=>{
dispatch(listPoduct())
},[dispatch]);
if (loading) {
return (<div>Loading...</div>);
}
if (error) {
return (<div>Error: {error}</div>);
}
console.log(products && products.length && products[0].img)
return(
<div>
...
</div>
)
}

Redux with getStaticProps

I'm struggling to understand how I can get around this issue?
I want to get data that I can share globally using redux**(using redux as I'm using it for other use cases in my app)**. my problem is I'm using getStaticProps to try and dispatch my ReduxThunk but I can't use Hooks inside getStaticProps and I have no idea what the workaround would be if anyone could point me to some docs I would appreciate it
Slice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchData = createAsyncThunk(
"fetchCoinData",
async (url, thunkApi) => {
const data = await fetch(url).then((res) => res.json());
return data;
}
);
const initialState = {
data: [],
status: null,
};
const getData = {};
export const dataSlice = createSlice({
name: "datafetch",
initialState,
extraReducers: {
[getData.pending]: (state) => {
state.status = "Loading!";
},
[getData.fulfilled]: (state, { payload }) => {
state.data = payload;
state.status = "Sucsess!";
},
[getData.rejected]: () => {
state.status = "Failed";
},
},
});
// Action creators are generated for each case reducer function
export const {} = dataSlice.actions;
export default dataSlice.reducer;
cardano.js
import React from "react";
import { useDispatch } from "react-redux";
import BasicCard from "../../Components/UI/Cards/BasicCard";
import { UsersIcon } from "#heroicons/react/outline";
import { fetchData } from "../../redux/slice/DataSlice";
const cardano = (props) => {
return (
<div>
<h1>Header</h1>
</div>
);
};
//PROBLEM IS HERE
export async function getStaticProps(context) {
const dispatch = useDispatch();
const priceQuery =
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin%2Ccardano%2Cethereum&vs_currencies=USD";
const res = await dispatch(fetchData(priceQuery));
return {
props: {
theData: res,
}, // will be passed to the page component as props
};
}
export default cardano;
To use Hooks, it has to be located at the highest level. Another alternative that you may try is the lifecycle of React. They are the same as Hooks.
For something like this, you probably want to use https://github.com/kirill-konshin/next-redux-wrapper .
Generally you have to be aware though: just putting something in the global state during SSR does not mean your client has it - your client will have it's own Redux state, and each page that is server-side-rendered also has their own isolated Redux state and you will think about what of that you will "hydrate" from the server to the client to make it available there.
The docs of next-redux-wrapper go more into this than I could possibly explain myself, so give those a read!

Redux - dispatch is not being called

I'm wrapping my head around this and cannot figure out why it's not working.
I have a simple action:
export const GET_EXPENSE_LIST = "GET_EXPENSE_LIST"
export const getExpenseList = () => {
return async dispatch => {
console.log('Action called')
dispatch({ type: GET_EXPENSE_LIST, expenseList: []})
}
}
And a reducer:
import { GET_EXPENSE_LIST } from "../actions/expenses"
const initialState = {
expenseList = []
}
export default (state = initialState, action) => {
console.log("reducer called")
}
I'm calling the action from a component like so (if it matters):
useEffect(() => {
dispatch(expensesActions.getExpenseList())
}, [dispatch]);
In my console I do see the "action called" but I don't see the "reducer called". Why the reducer is not being called?
Did you add thunk middleware to the config of redux? In you action you use redux-thunk

Redux Thunk action with axios returning multiple values

I have a React app that uses redux-thunk and axios to fetch an API. The action fires successfully, but returns multiple payloads which means it is firing multiple times.
How can I make it fire only one time?
Code
Actions
import Axios from "axios";
import { fetchEnglishLeagueTable } from "./ActionTypes";
export function fetchEnglishTable() {
var url = "https://api.football-data.org/v2/competitions/PL/matches";
var token = "52c8d88969d84ac9b17edb956eea33af";
var obj = {
headers: { "X-Auth-Token": token }
};
return dispatch => {
return Axios.get(url, obj)
.then(res => {
dispatch({
type: fetchEnglishLeagueTable,
payload: res.data
});
})
.catch(e => {
console.log("Cant fetch ", e);
});
};
}
Reducers
import { fetchEnglishLeagueTable } from "../actions/ActionTypes";
const initialState = {
EnglishTable: {}
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case fetchEnglishLeagueTable:
return {
...state,
EnglishTable: action.payload
};
default:
return state;
}
};
export default rootReducer;
Page
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, [props.leagueTable]);
console.log(props.leagueTable);
return <p>ihi</p>;
};
const mapStateToProps = state => ({
leagueTable: state.EnglishTable
});
const mapDispatchToProps = dispatch => {
return { getLeagueTable: () => dispatch(fetchEnglishTable()) };
};
export default connect(mapStateToProps, mapDispatchToProps)(League);
Store
import rootReducer from "./Reducer";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Here is what it returns
Just remove leagueTable from useEffect's dependency array, so it'll fetch them only once component is mounted. Because now you have a loop:
Get leagues -> leagueTable updates -> useEffect sees that leagueTable changed in dependency array and calls to get leagues again and we've got a loop.
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, []); // <~ no props.leagueTable here
console.log(props.leagueTable);
return <p>ihi</p>;
};
Hope it helps :)

Resources