How to use enhance store within redux middleware? - redux

I am building a React-Redux application and need a middleware function that has access to an enhanced store. I am unable to get the enhanced store to be available in the middleware function. Is this possible, if so how?
https://codesandbox.io/s/redux-enhanced-store-in-middleware-e1c5uv?file=/src/main.ts
import {createElement} from 'react'
import {Provider, useDispatch} from 'react-redux'
import {configureStore, getDefaultMiddleware} from '#reduxjs/toolkit'
import { createRoot } from 'react-dom/client'
function reducer(state, action){
console.debug("reducer...")
return state
}
const apiMiddleware = (store) => (next) => (action) => {
console.debug("apiMiddleware", store) // I would like store.api here
return next(action)
}
const storeEnhancer = (next) => {
const api = {doSomething: () => console.debug("api.doSomething")}
return (reducer, initialState) => {
const the_store = {api, ...next(reducer, initialState)}
console.debug("storeEnhancer", the_store)
return the_store
}
}
const store: any = configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(apiMiddleware),
enhancers: (defaultEnhancers) => [...defaultEnhancers, storeEnhancer],
})
const ClickButton = () => {
const dispatch = useDispatch()
const onClick = () => dispatch({type: "action"})
return createElement("button", {onClick}, "clicky")
}
export const app = () =>
{
const rootElement = document.getElementById("root")
const root = createRoot(rootElement!)
root.render(createElement(Provider, {store, children: createElement(ClickButton)}))
return createElement("div", {}, "hello")
}

Middleware don't get the entire Redux store as their outermost argument. Instead, they get a partial version - just {dispatch, getState}.
This is why I prefer to refer to that variable as storeApi, rather than store, because it isn't the entire store:
https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
So yeah, if your enhancer is attaching extra fields to the store instance, you can't access those in the middleware.

Related

React-redux Toolkit: Cannot set new state, when passing reducer as prop to another function

I am trying to use react redux toolkit and pass setter function to set new state on firebase's 'onAuthStateChanged'. The plan was to pass user's state (object or null) to reducer, depending if user is logged in or logged out. This is my first usage of redux, so I can't get why my code doesn't work. There is no errors, but in redux devtools state is always equal to null.
Configure Store:
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux';
import userReducer from './utils/userReducer';
const store = configureStore({
reducer: {
user: userReducer,
}
})
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
My reducer:
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: 'user',
initialState: null,
reducers: {
setUser: (state, action) => {
state = action.payload;
}
}
})
export const {setUser} = userSlice.actions;
export default userSlice.reducer;
Where I am dispatching it:
import { setUser } from '../utils/userReducer'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleLogin = async (e) => {
e.preventDefault()
const { user } = await logInWithEmail(email, password)
await setCurrentUser(() => dispatch(setUser))
}
Firebase function, where I am trying to use reducer:
export const setCurrentUser = async (setUser) => {
await onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser)
})
}
I understand, that with useContext it would be much easier, but I am trying to learn redux by implying it.
Try like that:
import { setUser } from '../utils/userReducer'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleLogin = async (e) => {
e.preventDefault()
const { user } = await logInWithEmail(email, password)
// This line updated
await setCurrentUser((currentUser) => dispatch(setUser(currentUser)))
}
The reason:
your setCurrentUser function prop setUser is just function () => dispatch(setUser), but this function does not receive any prop, and dispatch(setUser) does not do anything. you need to pass value (payload) to reducer function.
Additionally, try passing dispatch itself as prop and dispatch inside of onAuthStateChanged.
export const setCurrentUser = async (dispatch) => {
await onAuthStateChanged(auth, (currentUser) => {
dispatch(setUser(currentUser))
})
}
import setUser reducer function if handleLogin and setCurrentUser function is in different files separately.

Redux actions to reducers not showing in devtools state

I'd managed to get some of my earlier functions state in devtools as below:
Reducers function in DevTools
But when I tried to query some of the events in my interactions, the functions state werent able to display it. Below are my codes and settings, basically the flow is interactions > actions > reducers
interaction code:
export const loadAllOrders = async (exchange, dispatch) => {
// Fetch cancelled orders with the "Cancel" event stream
const fromBlock = 0;
const toBlock = "latest";
const cancelFilter = exchange.filters.CancelOrder();
const cancelStream = await exchange.queryFilter(cancelFilter, fromBlock, toBlock);
console.log(cancelStream)
// Format cancelled orders
const cancelledOrders = cancelStream.map((event) => event.args);
// Add cancelled orders to the redux store
dispatch(cancelledOrdersLoaded(cancelledOrders));
}
from my actions:
export const cancelledOrdersLoaded = (cancelledOrders) => {
return {
type: 'CANCELLED_ORDERS_LOADED',
payload:cancelledOrders
}
}
from my reducers:
export const exchange = (state = initialState, action) => {
switch (action.type) {
case 'EXCHANGE_LOADED':
return { ...state, loaded:true, contract: action.payload }
case 'CANCELLED_ORDERS_LOADED':
return { ...state, cancelledOrders: action.payload }
default:
return state
}
my configureStore
// For redux dev tools
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
const store = createStore(
rootReducer,
compose(applyMiddleware(thunk),devTools)
)
Thanks in advance
I haven't worked with redux for quite some time now, but from a quick look at some of my older repos, it seems like you didn't set up your store correctly.
This is what I have there,
import { applyMiddleware, createStore, compose, combineReducers } from "redux"
import thunk from "redux-thunk"
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const rootReducer = combineReducers({
reducers...
})
export const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)))

How to implement redux-toolkit and next,js and not lose SSR

I'm trying to implement redux-toolkit in my Next.js project without losing the option of SSR for the data I'm fetching from an external API. I have followed the example in next.js GitHub but doing so led to not having SSR when fetching data in my redux slice. I would like to know how I can fetch data and save that data in the Redux state. this is what I've written:
this is the store.js file
export const store = configureStore({
reducer: {
users: usersReducer,
},
});
the _app.js file
import { Provider } from 'react-redux';
import { store } from '../app/store';
const MyApp = ({ Component, pageProps }) => {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
};
export default MyApp;
the usersSlice.js file
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
items: null,
status: 'idle',
error: null,
};
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await res.json();
return users;
});
const usersSlice = createSlice({
name: 'categories',
initialState,
reducers: {},
extraReducers: {
[fetchUsers.pending]: (state, action) => {
state.status = 'loading';
},
[fetchUsers.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.items = action.payload;
},
[fetchUsers.rejected]: (state, action) => {
state.status = 'failed';
state.error = action.error.message;
},
},
});
export default usersSlice.reducer;
and finally where the page I'm fetching the data from:
export default function Home() {
const dispatch = useDispatch();
const users = useSelector((state) => state.users.items);
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
return (
<div>
<h1>users</h1>
{users &&
users.length &&
users.map((user) => <p key={user.id}>{user.name}</p>)}
</div>
);
}
If I fetch data through dispatching the fetchUsers function it won't have SSR, and if I use getServerSideProps it won't be saved in redux state. I'm clueless
If you are okay to use Redux instead of redux-toolkit then follow this example
[https://github.com/vercel/next.js/blob/canary/examples/with-redux-persist]Officail Example form Vercel/next.js. I too facing some issue when i am writing SSR code for Redux persist with redux toolkit. The work is currently in progress. Will share the code when it is available. Sis

Redux Dispatch Infinite Loop

I am using axios.get in my useeffect and then I am passing the data from response to the dispatcher. It retrieves data and dispatches and then I get the state to show data an console it but data shows infinite loop. Here is my useEffect, local state, mapStateToProps and mapDispatchToProps.
const [users, setUsers] = useState()
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(response => {
// console.log(response.data)
__storeUsers(response.data)
})
setUsers(showUsers)
console.log(users)
}, [__storeUsers, showUsers, users, setUsers])
const mapStateToProps = state => ({
showUsers: state.getUsers.users
})
const mapDispatchToProps = dispatch => ({
__storeUsers: (data) => dispatch({type: types.STORE_USERS, payload: data}),
})
This is my reducer for users
import * as types from "./types";
const initialState = {
users: []
}
const usersState = (state = initialState, action) => {
switch (action.type) {
case types.STORE_USERS:
return {
...state,
users: action.payload
}
default:
return state
}
}
export default usersState
This is for practice purpose. i am not using actionCreators right now. After this I will move the axios call to the action creator. The data that I get from above goes in loop in console. Please help.
Also if I create action creator for this, that also goes in loop. Action creator is like below:
export const UserActions = () => async (dispatch) => {
const response = await axios.get('https://jsonplaceholder.typicode.com/users')
if (response.data) {
// console.log(response.data)
dispatch({
type: types.STORE_USERS,
payload: response.data
})
} else {
// console.log("no data")
}
return response
}
And then I use it like below
const mapDispatchToProps = dispatch => ({
__storeUsers: () => dispatch(UserActions())
})
Both methods are firing loop in console.
Notice that your useEffect here is where the infinite loop occurs:
const [users, setUsers] = useState()
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(response => {
// console.log(response.data)
__storeUsers(response.data)
})
setUsers(showUsers)
console.log(users)
}, [__storeUsers, showUsers, users, setUsers])
The useEffect has been told that users is one of its dependencies and that it should re-run when this variable changes. The useEffect then changes the value of users via setUsers and it sees this update so runs again.
It looks like you're only depending on users for this console.log. Consider taking it out of the dependency list.

React-Redux: Error: Actions must be plain objects. Use custom middleware for async actions

I've read multiple sources about this error but I cannot figure out what I'm doing incorrectly here. I'm using custom middleware already and I believe that I'm returning the action correctly. Any advice?
app.js
import React from "react";
import ReactDOM from "react-dom";
import { renderToString } from "react-dom/server";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
import DataProvider from "./DataProvider";
import QuestionContainer from "./QuestionContainer";
import * as actions from "../actions";
const App = () => <QuestionContainer />;
const store = createStore(
rootReducer,
applyMiddleware(thunk)),
);
store
.dispatch(actions.fetchQuestions())
.then(() => response.send(renderToString(<Provider store={ store }><App /></Provider>)))
Then in actions.js
export function fetchQuestions() {
return (dispatch) => {
return fetch('/api/questions')
.then(response => response.json())
.then(data => dispatch(loadRequestData(data)),
)
}
}
The error showing in browser console:
redux.js:208 Uncaught (in promise) Error: Actions must be plain objects. Use custom middleware for async actions.
at dispatch (redux.js:208)
at eval (index.js:12)
at dispatch (redux.js:571)
at eval (actions.js:35)
I think there's something's wrong in this part of code:
store
.dispatch(actions.fetchQuestions())
.then(() => response.send(renderToString(<Provider store={ store }><App /></Provider>)))
When you're creating async calls you want to do this only in action, not reducer/store.
So you need to delete this line .then(() => response.send(renderToString(<Provider store={ store }><App />
and instead of that just make:
const app = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(app, document.getElementById('root'));
Additionally make some actions which will be kind of helper for updating store in your reducer. Something like this:
export const fetchBegin = () => ({
type: 'FETCH_BEGIN'
})
export const fetchSuccess = (payload) => ({
type: 'FETCH_SUCCESS'
payload
})
export const fetchQuestions = () => {
return (dispatch) => {
dispatch(fetchBegin())
return fetch('/api/questions')
.then(response => response.json())
.then(data => dispatch(fetchSuccess(data))
)
}
}
Then in the reducers make:
const initialState = {
call: [],
loading: false
}
const reducer = (state = initialState, action){
switch(action.type){
case 'FETCH_BEGIN:
return{
...state,
loading: true,
case 'FETCH_SUCCESS':
return{
...state,
call: action.payload,
loading: false,
}
default:
return state
}
}
This should work imho.

Resources