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.
Related
I am trying to define an Async Thunk method that calls a basic async function and returns the data. I am going to subsequently use this data in multiple components.
I cannot achieve this without the Async Thunk method calling the API as many times as I have components using the method.
import React from 'react';
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { useDispatch, useSelector } from 'react-redux'
// Async Thunk Method
const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async (thunkAPI) => {
console.log("Calling fetch")
const response = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Done!")
}, 1000)
})
return response
}
)
const initialState = {
entities: [],
loading: 'idle',
}
const usersSlice = createSlice({
name: 'users',
initialState,
extraReducers: (builder) => {
builder.addCase(fetchUsers.fulfilled, (state, action) => {
return {
...state,
entities: action.payload,
loading: 'fulfilled'
}
})
},
})
export function useUsers() {
const dispatch = useDispatch()
const { entities, loading } = useSelector(state => state.users);
console.log(loading)
React.useEffect(() => {
if (loading === 'idle') {
dispatch(fetchUsers())
}
}, [loading]);
return React.useMemo(() => ({
entities
}), [entities])
}
export default usersSlice.reducer;
function App() {
/* This will cause the Async-Thunk to be called twice
* (goal is to be called once)
*/
useUsers();
useUsers();
}
I will be using this hook in multiple components, and for some reason, they are unable to see that the loading status is not idle after the first call, so many calls are made.
//Store
import { configureStore } from "#reduxjs/toolkit";
import { currencyListSlice } from "./Reducers/CurrencyListReducer";
export const store = configureStore({
reducer: {
currencyList: currencyListSlice.reducer,
}
}
)
export default store
//CurrencyListReducer
import { createSlice } from "#reduxjs/toolkit"
export const loadCurrencyList = () => {
return async (dispatch, getState) => {
const data = await fetch(API-Key)
const payload = await data.json()
dispatch({
type: 'currencyList/setCurrencyList',
payload: payload
})
}
}
const options = {
name: 'currencyList',
initialState: [],
reducers: {
setCurrencyList(state, action) {
return action.payload
}
}
}
export const currencyListSlice = createSlice(options)
//CurrencyList Component
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { currencyListSlice } from '../../Reducers/CurrencyListReducer'
const selectCurrencyList = state => state.CurrencyList
export const CurrencyList = () => {
const dispatch = useDispatch()
const currencyList = useSelector(selectCurrencyList)
const { loadCurrencyList } = currencyListSlice.actions
useEffect(() => {
dispatch(loadCurrencyList())
}, [dispatch, loadCurrencyList])
console.log(currencyList)
return (
<div>
/*Some elements here*/
</div>
)
}
I'm working with redux for the first time and having some real problem in calling API and storing data in store. The problem is I'm not getting anything from API but the console.log(currencyList) just gives me undefined. I tried calling API directly in reducer but that too didn't work out. I'm a newbie to redux and calling the API in redux is being a difficult task for me. Forgive any silly mistake(if present).
try reading this: createAsyncThunk
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!
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
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 :)