Store State Issues - Next Redux Wrapper vs Redux ToolKit - next.js

FYI: Everything is working fine and all the flows are working as expected but logs are confusing.
I am using Redux ToolKit for managing redux state & using Next Redux Wrapper for Server Side rendering and caching queries.
CartSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const cartSlice = createSlice({
name: 'cart',
initialState: { data: [] },
reducers: {
addToCart: (state, action) => {
const itemInCart = state.data.find((item) => item.id === action.payload.id);
if (itemInCart) {
itemInCart.quantity += action.payload.quantity;
} else {
state.data.push({ ...action.payload });
}
}
},
});
export const cartReducer = cartSlice.reducer;
export const { addToCart } = cartSlice.actions;
ServiceApi.js
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
import { HYDRATE } from 'next-redux-wrapper'
export const serviceApi = createApi({
reducerPath: 'serviceApi',
baseQuery: fetchBaseQuery({ baseUrl: '<base-url>' }),
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === HYDRATE) {
return action.payload[reducerPath]
}
},
endpoints: (builder) => ({
getItemById: builder.query({ query: (id) => `id/${id}` }),
getItems: builder.query({ query: () => `/` }),
}),
})
export const { getRunningQueriesThunk } = serviceApi.util
export const { useGetItemByIdQuery, useGetItemsQuery } = serviceApi
export const { getItemById, getItems } = serviceApi.endpoints;
store.js
import { configureStore } from '#reduxjs/toolkit'
import { serviceApi } from './serviceApi'
import { createWrapper } from "next-redux-wrapper";
import { cartSlice } from "./cartSlice";
export const store = configureStore({
reducer: {
[cartSlice.name]: cartSlice.reducer,
[serviceApi.reducerPath]: serviceApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(serviceApi.middleware),
})
const makeStore = () => store
export const wrapper = createWrapper(makeStore, {debug: true});
Using getServerSideProps
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async (context) => {
const { id } = context.query;
store.dispatch(serviceApi.endpoints.getItemById.initiate(parseInt(id)));
await Promise.all(store.dispatch(serviceApi.util.getRunningQueriesThunk()));
return {
props: {},
};
}
);
And using wrappedStore
const { store, props } = wrapper.useWrappedStore(pageProps);
On the UI flows everything is working as expected and i am able to add items in the cart and i can see the store state is getting updated by looking the UI.
But the logs from next redux wrapper is confusing:

Related

TypeError: (0 , _react.createContext) is not a function on Nextjs using Redux

im new in using redux and tried to use it with the help of online tutorials. nut im seeing this error:
Server Error TypeError: (0 , _react.createContext) is not a function
This error happened while generating the page. Any console logs will be displayed in the terminal window.
im providing my codes bellow:
authSlice.js file:
import { createSlice } from "#reduxjs/toolkit";
import { HYDRATE } from "next-redux-wrapper";
const initialState = {
authState: false,
userInfo: {},
authToken: ""
}
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setAuthState(state, action) {
state.authState = action.payload
}
, setUserInfo(state, action) {
state.userInfo = action.payload
},
setToken(state, action) {
state.authToken = action.payload
},
setAuth(state, action) {
state.authState = action.payload.authState;
state.userInfo = action.payload.userInfo;
state.authToken = action.payload.authToken;
}
},
extraReducers: {
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.auth
}
}
}
})
export const { setAuthState, setUserInfo, setToken, setAuth } = authSlice.actions
export const selectAuthState = (state) => state.auth.authState;
export const selectAuthToken = (state) => state.auth.authToken;
export const selectUserInfo = (state) => state.auth.userInfo;
export default authSlice.reducer
store.js codes:
import { configureStore, ThunkAction, Action } from "#reduxjs/toolkit";
import { authSlice } from "./authSlice";
import { createWrapper } from "next-redux-wrapper";
const makeStore = () =>
configureStore({
reducer: {
[authSlice.name]: authSlice.reducer,
},
devTools: true,
});
const store = makeStore()
export default store
components where i tried to use redux:
import { useRouter } from "next/navigation";
import { useDispatch, useSelector } from "react-redux";
import {
selectAuthState,
selectAuthToken,
selectUserInfo,
setAuthState,
setUserInfo,
} from "#/data/authSlice";
function Sidebar() {
const router = useRouter();
const dispatch = useDispatch();
const authState = useSelector(selectAuthState);
const authToken = useSelector(selectAuthToken)
const userInfo = useState(selectUserInfo)
useEffect(() => {
if (!authState) {
router.push("/")
}
}, [router.asPath]);
// rest of the codes
import axios from "axiosConfig";
import { useDispatch, useSelector } from "react-redux";
import {
selectAuthState,
selectAuthToken,
setAuthState,
setUserInfo,
} from "#/data/authSlice";
// toastify
// import { toast } from 'react-toastify';
// import 'react-toastify/dist/ReactToastify.css';
// const notify = (text) => {
// toast.success(text , {position: toast.POSITION.BOTTOM_RIGHT})
// }
const LoginPage = () => {
const router = useRouter();
const dispatch = useDispatch();
const authState = useSelector(selectAuthState);
useEffect(() => {
const token = authState;
if (token) {
router.push("/Dashboard");
} else {
}
}, [router.asPath]);
// rest of the codes
i tried to reinstall redux and react-redux but didnt helped.
also tried Remove the node_modules
Remove package-lock.json
npm install again.
but nothing happend.

localStorage.getItem is not working on nextjs, using redux and material ui

i am trying to save user settings, [dark mode/ light mode] on local storage using redux on nextjs
I can save the data on the local storage but i can't pull the data into the initialSatte. here is my code
import { createSlice } from "#reduxjs/toolkit";
import Cookies from "js-cookie";
const getFromLocalStorage = (key: string) => {
if (!key || typeof window === "undefined") {
return "";
}
try {
// #ts-ignore
return JSON.parse(localStorage.getItem(key)) || {};
} catch (error) {
return {};
}
};
export const uiSettings = createSlice({
name: "uiSettings",
initialState: {
theme: getFromLocalStorage("uiSettings")?.theme || "dark",
},
reducers: {
themeSwitch: (state, action) => {
state.theme = action.payload;
window.localStorage.setItem(
"uiSettings",
JSON.stringify({
theme: action.payload,
})
);
},
},
extraReducers: (builder) => {},
});
export const uiSettingsReducer = uiSettings.reducer;
export const { themeSwitch } = uiSettings.actions;
i tried alot of ways to solve it but it didn't work.

next-redux-wrapper HYDRATION failed

I am trying to integrate next-redux-wrapper to Next.js RTK project. When invoking async action from getServerSideProps, I am getting state mismatch error (see the image below).
When I dispatch action from client side (increment/decrement), everything works well. I think issue is related to HYDRATION but so far all my efforts have failed.
I tried mapping redux state to props, storing props in component state, added if statements to check values but nothing seem to work. I've been stuck on this for 2 weeks. I'm not sure what else to try next.
"next": "12.3.1",
"next-redux-wrapper": "^8.0.0",
"react":
"18.2.0",
"react-redux": "^8.0.4"
store/store.js
import { configureStore, combineReducers } from "#reduxjs/toolkit";
import counterReducer from "./slices/counterSlice";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
const combinedReducer = combineReducers({
counter: counterReducer,
});
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
export const makeStore = () =>
configureStore({
reducer,
});
export const wrapper = createWrapper(makeStore, { debug: true });
store/slices/counterSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
value: 0,
data: { quote: "" },
pending: false,
error: false,
};
export const getKanyeQuote = createAsyncThunk(
"counter/kanyeQuote",
async () => {
const respons = await axios.get("https://api.kanye.rest/");
return respons.data;
}
);
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(getKanyeQuote.pending, (state) => {
state.pending = true;
})
.addCase(getKanyeQuote.fulfilled, (state, { payload }) => {
state.pending = false;
state.data = payload;
})
.addCase(getKanyeQuote.rejected, (state) => {
state.pending = false;
state.error = true;
});
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
pages/index.js
import React, { useState } from "react";
import { useSelector, useDispatch, connect } from "react-redux";
import {
decrement,
increment,
getKanyeQuote,
} from "../store/slices/counterSlice";
import { wrapper } from "../store/store";
function Home({ data }) {
const count = useSelector((state) => state.counter.value);
// const { data, pending, error } = useSelector((state) => state.counter);
const dispatch = useDispatch();
const [quote, setQuote] = useState(data.quote);
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{/* <span>{pending && <p>Loading...</p>}</span>
<span>{error && <p>Oops, something went wrong</p>}</span> */}
<div>{quote}</div>
<span>Count: {count}</span>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
);
}
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req, res, ...etc }) => {
console.log(
"2. Page.getServerSideProps uses the store to dispatch things"
);
await store.dispatch(getKanyeQuote());
}
);
function mapStateToProps(state) {
return {
data: state.counter.data,
};
}
export default connect(mapStateToProps)(Home);
Errors in console
This might stem from a known issue where next-redux-wrapper 8 hydrates too late. Please try downgrading to version 7 for now and see if that resolves the problem.

React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhausCompiled with warnings

Error while dispatching an action!
React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhausCompiled with warnings.
App.js :-
function App() {
const user = null;
const dispatch = useDispatch();
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
if (userAuth) {
dispatch(login({
uid: userAuth.uid,
email: userAuth.email
}));
} else {
dispatch(logout);
}
})
return unsubscribe;
}, []);
userSlice.js :-
export const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
}
},
logout: (state) => {
state.user = null;
}
});
export const { login, logout } = userSlice.actions;
store.js :-
import { configureStore } from '#reduxjs/toolkit';
import userReducer from '../features/userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});

Access redux state value on a React + Redux + Hooks + Typescript web app

I am trying to access the redux state to display its value on my website. I am using React redux hooks with functional components and Typescript.
Situation:
I have a store with two reducers: UI and user. The initial state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: null
}
}
When the user signs in, the signinUser action takes place and correctly changes the redux state. For example, for an invalid signin, the redux state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: {
general: 'wrong credentials, please try again'
}
}
}
Problem:
I am trying to acces the UI.errors so I can display them on my website. i have a function in my Signin component thnamed submitForm that calls the signinUser action that correctly dispatches the actions. My problem is that after that I want to retrieve the state.ui.errors and I can't figure out how to.
I have tried all this:
componentWillRecieveProps(nextProps) { ... } this solution is for class components and I am using functional components
useSelector((state: StoreState) => state.UI); If I do it inside submitForm is invalid because React Hooks don't allow to call inside a function. If I do it outside, it fetches the old state.
Here are my files (the parts related to this issue)
store.tsx
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
// Reducers
import userReducer from './reducers/userReducer';
import uiReducer from './reducers/uiReducer';
const initialState = {};
const middleware = [thunk];
const reducers = combineReducers({
user: userReducer,
UI: uiReducer
});
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(...middleware),
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
userActions.tsx
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../types';
import axios from 'axios';
// Interfaces
import { ISigninForm } from '../../utils/types';
// Redux
import { Dispatch } from 'redux';
import { useDispatch } from 'react-redux';
export const signinUser = (
userData: ISigninForm,
dispatch: Dispatch,
handleDialogClose: () => void
) => {
console.log('signinuser in userActions');
dispatch({ type: LOADING_UI });
axios
.post('/signin', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
getUserData(dispatch);
dispatch({ type: CLEAR_ERRORS });
handleDialogClose();
// history.push("/profile"); // this will redirect to a page not built yet
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
});
});
};
export const getUserData = (dispatch: Dispatch) => {
console.log('getUserData');
axios
.get('/user')
.then((res) => {
console.log('/user', res);
dispatch({
type: SET_USER,
payload: res.data
});
})
.catch((err) => console.log('err', err));
};
uiReducer.tsx
import { SET_ERRORS, CLEAR_ERRORS, LOADING_UI, IAction } from '../types';
const initialState = {
loading: false,
errors: null
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_ERRORS:
return {
...state,
loading: false,
errors: action.payload
};
case CLEAR_ERRORS:
return {
...state,
loading: false,
errors: null
};
case LOADING_UI:
return {
...state,
loading: true
};
default:
return state;
}
}
userReducer.tsx
import {
SET_USER,
SET_AUTHENTICATED,
SET_UNAUTHENTICATED,
IAction
} from '../types';
const initialState = {
authenticated: false,
credentials: {}
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState;
case SET_USER:
console.log('SET_USER', action);
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
Signin.tsx
function Signin({ history }: RouteComponentProps): JSX.Element {
// States
const [dialogOpen, setDialogOpen] = React.useState(false);
const [errorsAPI, setErrorsAPI] = React.useState<ISigninErrors>({});
const [loading, setLoading] = React.useState(false);
// Dialog
const handleDialogOpen = () => {
setDialogOpen(true);
};
const handleDialogClose = () => {
setDialogOpen(false);
};
// Form
const { register, handleSubmit, errors } = useForm<ISigninForm>();
const submitForm = (data: ISigninForm) => {
signinUser(data, dispatch, handleDialogClose);
};
return (
// HTML content
);
}
export default withRouter(Signin);
Solution:
I had the solution in front of me this whole time, but I was not using the function in the right way.
const state = useSelector((state: StoreState) => state);
This is called inside the Signin function component. Then when I am returning the HTML object, I just call
{state.UI.errors !== null && 'general' in state.UI.errors && (
<p>{state.UI.errors.general}</p>
)}

Resources