Redux with getStaticProps - reactjs

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!

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.

mutating state in redux toolkit

My reducer mutate the state, which i dont want, please help
#Supplier Slice
import { createSlice,createAsyncThunk,createEntityAdapter } from '#reduxjs/toolkit'
import axios from 'axios'
export const getSupplierData = createAsyncThunk(
'supplier/getSupplierData', async () => {
const response = axios.get(
"http://127.0.0.1:8000/supplier/"
);
const data = await (await response).data
return data
})
const suppliersAdapter = createEntityAdapter({})
export const { selectAll: selectSuppliers, selectById: selectSupplierById} = suppliersAdapter.getSelectors(
state => state.suppliers
)
export const saveSupplier = createAsyncThunk('supplier/saveSupplier',
async supplier => {
const response = await axios.post('http://127.0.0.1:8000/supplier/', supplier)
const data = await response.data
return data
}
)
export const suppliersSlice = createSlice({
name: 'suppliers',
initialState:suppliersAdapter.getInitialState({}),
reducers: {
newSupplier:(state,action)=>action.payload
},
extraReducers: {
[getSupplierData.fulfilled]: suppliersAdapter.setAll,
[saveSupplier.fulfilled]:(state,action)=>state.push
}
})
export const { newSupplier } = suppliersSlice.actions
export default suppliersSlice.reducer
#supplier
const onSubmit=data=>dispatch(saveSupplier(data))
When i call this it replaced the list of supplier with the new data passed from the form
It mutates the state, which is not desireable
You need to remove [saveSupplier.fulfilled]:(state,action)=>state.push from your extraReducers. What you're doing now is trying to set the value to a function, which is why you'd also see a non-serializable error in your console with this code. If you just want to leave it as a placeholder and not update anything, just do [saveSupplier.fulfilled]: (state, action) => {}. Either way, I imagine what you actually want is [saveSupplier.fulfilled]: suppliersAdapter.addOne there?

Redux dispatch in useEffect isn't syncronous

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

Get Data using Redux with react hooks

So I just started learning hooks and I'm trying to use Redux with them. Ive been trying redux new api but now I can't seem to get the data that I want. When I do console.log() I see the promise getting resolved and inside the [[PromiseValue]] I see the data but how do I get it out of there into my Component.
const Main = ({props}) => {
const [cloth,setCloth]=useState([])
const items = useSelector((state) => state);
const dispatch = useDispatch()
let getItems = getAllItems(
() => dispatch({ type: 'GET_ITEMS' }),
[dispatch]
)
console.log(getItems)
here is the pic of my console.log()
first to use redux with async actions you need to use redux-thunk middleware:
npm i redux-thunk
and then use it like so:
import { createStore, applyMiddleware } from 'redux';
const store = createStore(rootReducer, applyMiddleware(thunk));
now for the actions here a simple example:
first action to fetch Items and then action to set Items in store:
//actions.js
const setItems = (payload)=>({type:SET_ITEMS,payload})
export const fetchItem = ()=>{
return dispatch =>{
axios.get("/items").then(response=>
dispatch(setItems(response.data))).catch(error=>throw error)
}
}
//reducer.js
const state = {items:[]}
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case "SET_ITEMS":
return {
...state,
items: payload
};
default:
return state;
}
}
now from react component we fetch items on mount :
import React,{useEffect} from "react"
import {useDispatch,useSelector} from "react-redux"
import {fetchItems} from "action.js"
const Items = ()=>{
const items= useSelector(state=>state.items)
const dispatch = useDispatch()
useEffect(()=> dispatch(fetchItems()),[])
return items?items.map(item =><p key={item.name}>{item.name}</p>):<p>items not available</p>
hope this what you are looking for.

Typing middleware with redux-toolkit

I'm developing a webapp using TypeScript and redux-toolkit.
tl;dr
How to the set the type for dispatch parameter on a middleware?
I need to fetch some data from my server and save it on my store. So I wrote a code like bellow (simplified):
import { createSlice } from '#reduxjs/toolkit'
import { TNewOrder } from '../store'
const initialState: TNewOrder = {
prices: [],
// other stuffs...
}
const newOrderSlice = createSlice({
initialState,
name: 'new-order',
reducers: {
setPrices(state, action) {
const { payload } = action
return {
...state,
prices: payload.prices
}
},
updateFormData(state, action) {
// not important...
}
}
})
const { setPrices, updateFormData } = newOrderSlice.actions
const fetchPrices = () =>
async (dispatch: any) => {
const response = await fetch('https://jsonbox.io/box_4186ae80994ed3789969/prices/5de7e570de45ab001702a382')
const { prices } = await response.json()
dispatch(setPrices({ prices }))
}
export { fetchPrices, updateFormData }
export default newOrderSlice.reducer
Note that, because to get fetch a data is an async task, I can't put it directly on setPrices. So I created a middleware called fetchPrices to do the request task and call setPrices.
Despite it works, I'm unhappy with this solution because I set an any type. How is the correctly way to set a better type? I can't found a way to import ThunkDispatch from redux-toolkit.
Is there a better way to do it?
ThunkDispatch is not exported by #reduxjs/toolkit. You should import it from redux-thunk if you want to use it.
import { ThunkDispatch } from 'redux-thunk';
const fetchPrices = () =>
async (dispatch: ThunkDispatch) => {
const response = await fetch('https://jsonbox.io/box_4186ae80994ed3789969/prices/5de7e570de45ab001702a382')
const { prices } = await response.json()
dispatch(setPrices({ prices }))
}
The official suggestion is to use typeof store.dispatch, as Redux-Toolkit's store.dispatch already includes ThunkDispatch. (And you can extend store.dispatch if you use additional middlewares and have everything in a central location then.)
In addition, please also take a look at the TypeScript documentation of Redux-Toolkit. If you miss anything imporant, please open an issue and let us know :)

Resources