I have 2 slices, the first of which contains state errors and the second of which contains logic.
Is it possible to change the value state in the error slice from a logical slice?
Error slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
error: false,
};
export const errorSlice = createSlice({
name: "error",
initialState,
reducers: {
setError: (state, action) => {
state.error = action.payload;
},
},
});
export const { setError } = errorSlice.actions;
export default errorSlice.reducer;
Logical slice
import { createSlice } from "#reduxjs/toolkit";
export const doSomething = (data) => {
return (dispatch) => {
dispatch(setData(data.text))
// here I want dispatch setError from errorSlice
// dispatch(setError(data.error))
};
};
const initialState = {
data: null,
};
export const logicalSlice = createSlice({
name: "logical",
initialState,
reducers: {
setData: (state, action) => {
state.error = action.payload;
},
},
});
export const { setData } = logicalSlice.actions;
export default logicalSlice.reducer;
And I need to run it from a component with a single dispatch
dispatch(doSomething(data))
Is there such a possibility?
Thank you!
Related
I am very new to RTK, so I am trying to create a store and a slicer.
At first, at least I want to fetch some data from an API so when it start loading and after being succeed, I know the state of it.
Here I am creatinf the slicer:
const initialState: PlayerState = {
players: [],
status: 'idle'
};
export const getPlayers = createAsyncThunk('players/getPlayers', async () => {
const response = await axios.get(
'https://6360055fca0fe3c21aaacc04.mockapi.io/player'
);
return response.data;
});
const playerSlice = createSlice({
name: 'players',
initialState,
reducers: {
addPlayer: (state, action: PayloadAction<IPlayerProps>) => {
console.log('done');
state.players.push(action.payload);
}
},
extraReducers: {
[getPlayers.pending]: (state, action) => {
console.log('loading');
state.status = 'loading';
},
[getPlayers.fulfilled]: (state, action) => {
console.log('succeeded');
state.status = 'succeeded';
state.players = state.players.concat(action.payload);
}
}
});
export const { addPlayer } = playerSlice.actions;
export const selectPlayers = (state: RootState) => state.players.payload;
And here I am trying to connect it to the store:
//#ts-nocheck
import { configureStore } from '#reduxjs/toolkit'
import { addPlayer } from './playerSlice'
export const store = configureStore({
reducer: {
players: addPlayer,
},
})
export type RootState = ReturnType<typeof store.getState>;
So, after that I have a page with a button, so when I click it I try to dispatch something out of it with no luck unfortunately:
const NextPage = () => {
const dispatch = useDispatch();
return (
<ButtonNext
onClick={() => {
dispatch(addPlayer);
}}
text="< Back"
/>
);
};
export default NextPage;
Any help would be appreciated! :)
The are several issues in your code
First fix your createAsyncThunk
export const getPlayers = createAsyncThunk('players/getPlayers'
async (_unusedArgs, _thunkApi) => {
const response = await fetch('http://localhost:3000/players')
return response.json()
}
)
Your slice should look like this, note the builder callbacks for the cases:
export const playerSlice = createSlice({
name: "players",
initialState,
reducers: {
addPlayer: (state, action) => {
console.log("done");
state.players.push(action.payload);
}
},
extraReducers: (builder) => {
builder.addCase(getPlayers.fulfilled, (state, action) => {
console.log(action.payload);
state.players = action.payload;
state.status = "idle";
});
builder.addCase(getPlayers.pending, (state, action) => {
console.log("loading");
state.status = "loading";
});
}
});
export default playerSlice.reducer;
Call it inside the anonymous fn
<ButtonNext
onClick={() => {
dispatch(getPlayers()); // call with no arguments.
}}
text="< Back"
/>
And I also think that your root reducer in store is not right
import playerSlice from './playerSlice' // defaulted export
export const store = configureStore({
reducer: {
players: playerSlice,
},
})
Please check this sandbox with working example: https://codesandbox.io/s/redux-toolkit-basic-players-w-pokemons-6wmjm0?file=/src/features/playerSlice.js:409-995
I am trying to auth session by random user with http get request and createAsyncThunk.
fetching the user data in App.js on mount hook.
I can see the get request in my network and the new fetched state in redux dev tool,
but my TopBar.js useSelector return the initial state before the fetch.
TopBar.js user log:
App.js:
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchRandomUserData())
}, [dispatch]);
authSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
isLoggedIn: true,
user: {},
loading: false,
error: null,
};
export const fetchRandomUserData = createAsyncThunk(
'auth/fetchRandomUser',
async () => {
try {
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
return data.results[0];
} catch (error) {
throw Error(error);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout(state, action) {
state.isLoggedIn = false;
},
},
extraReducers: {
[fetchRandomUserData.pending]: (state, action) => {
state.loading = true;
state.error = null;
},
[fetchRandomUserData.fulfilled]: (state, action) => {
console.log("action.payload",action.payload);
state.user = action.payload;
state.loading = false;
},
[fetchRandomUserData.rejected]: (state, action) => {
state.error = action.error.message;
state.loading = false;
},
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
store.js
import { configureStore } from '#reduxjs/toolkit';
// import booksReducer from './reducers/booksReducer';
import booksReducer from './slices/bookSlice';
import authReducer from './slices/authSlice';
const store = configureStore({
reducer: { books: booksReducer, auth: authReducer },
});
export default store;
TopBat.js:
export default function TopBar(): JSX.Element {
const user = useSelector((state: any) => state.auth);
console.log("topbar",user); // returns the initial state
//....
Please make sure that you update react-redux to version 8 if you are using react 18.
There are known cases where components just stop updating if you are using react-redux 7 and lower with react 18.
so I have auth reducer and loading reducer. I'd like to set the state in loading reducer whenever the createAsynchThunk in auth reducer is pending. the code look like this:
//auth reducer
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import {callPOSTSignInUserAccount} from "api"
export const signInRequest = createAsyncThunk(
"auth/login",
async (userData: UserDataLogin, thunkAPI) => {
try {
const result = await callPOSTSignInUserAccount(
userData.email,
userData.password
);
const auth = result.data.AuthenticationResult;
const user = result.data.user;
catch(err) {
const result = {
alert: {
type: "error",
message: errMsg
}
}
return thunkAPI.rejectWithValue(result)
}
}
//state
const authState = {
isAuthenticated = true,
errorSignIn = "",
auth: {},
};
//slice for auth
const sliceAuth = createSlice({
name: "auth",
initialState: authState,
reducers: {},
extraReducers: (builder) => {
//Sign in request
.addCase(signInRequest.pending, (state, action) => {
//set loading reducer state from here
})
.addCase(signInRequest.fulfilled, (state, action:any) => {
if (action.payload?.auth !== undefined) {
state.isAuthenticated = true
state.errorSignIn = ""
state.auth = action.payload.auth
}
})
.addCase(signInRequest.rejected, (state, action:any) => {
//also set alert here
})
}
const authReducer = sliceAuth.reducer
export default authReducer
//loading reducer
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const loadingState = {
appLoading: false,
};
const sliceLoading = createSlice({
name: "loading",
initialState: loadingState,
reducers: {
setLoading: (state, action) => {
state.apploading = action.payload
}
})
const reducerLoading = sliceLoading.reducer
export default reducerLoading
from what I read I can't dispatch an action in reducer because it's anti-pattern. I want to change the loading state in loading reducer from the auth reducer.
I can add loading in the auth reducer initial state but it become hard to manage whenever I have more than one reducer in a react component.
When i click the delete button the action(removeBus) is dispatched and the correct payload value is sent however the store is not updating
here is my busSlice file
import { createSlice } from "#reduxjs/toolkit";
const initialState = [
{
id: "0",
description: "bus1"
},
{
id: "1",
description: "bus2"
}
];
const busSlice = createSlice({
name: "bus",
initialState,
reducers: {
removeBus: (state, action) => {
state.filter((bus) => bus.id !== action.payload);
console.log(action.payload);
}
}
});
export const { removeBus } = busSlice.actions;
export default busSlice.reducer;
here is where i created and export the store
import { configureStore } from "#reduxjs/toolkit";
import busReducer from "../features/busSlice";
export const store = configureStore({
reducer: {
busses: busReducer
}
});
the component that uses the state is created in the following code and App is wrapped inside a redux provider Component and the above store is provided as a props in index.js
import "./styles.css";
import { useSelector, useDispatch } from "react-redux";
import { removeBus } from "./features/busSlice";
export default function App() {
const dispatch = useDispatch();
const busses = useSelector((state) => state.busses);
const handleRemove = (id) => {
dispatch(removeBus(id));
};
return (
<div className="App">
{busses.map((bus) => (
<ul>
<li key={bus.id}>
<p>{bus.description}</p>
<button onClick={() => handleRemove(bus.id)}>delete</button>
</li>
</ul>
))}
</div>
);
}
using redux dev tools i can see an action busses\removeBus is dispatched with the appropriate payload value but nothing is happening to the state.
You can change:
removeBus: (state, action) => {
state.filter((bus) => bus.id != action.payload);
console.log(action.payload);
}
can be the data types of bus.id and action.payload are different.
you are not updating state correctly you need to say change my state to filtered state
like this
removeBus(state, action){
state = state.filter((bus) => bus.id !== action.payload);
console.log(action.payload);
}
I also had this issue.
I my case, code looked like this:
const someSlice = createSlice({
name: 'test',
initialState: null,
reducers: {
addData(state, action) {
state = action.payload
}
}
});
This did not work
const someSlice = createSlice({
name: 'test',
initialState: null,
reducers: {
addData(state, action) {
state = action.payload;
return state;
}
}
});
this worked.
But this is very strange, should not work this way.
You need to return your updated states
import { createSlice } from "#reduxjs/toolkit";
const initialState = [
{
id: "0",
description: "bus1"
},
{
id: "1",
description: "bus2"
}
];
const busSlice = createSlice({
name: "bus",
initialState,
reducers: {
removeBus: (state, action) => {
state = state.filter((bus) => bus.id !== action.payload);
return state;
}
}
});
export const { removeBus } = busSlice.actions;
export default busSlice.reducer;
I am trying to create a notification Component.
My notification Component is at root level and when ever user tries to login the process of the the async function is relayed to him i.e. pending fulfilled or rejected.
The Problem is that I don't know how to call notification reducer from userSlice or even if its possible is this a good way or not.
User Slice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialUserState = {
currentUser:null
}
export const getUser = createAsyncThunk(
'user/getUser',
async (endpoint, data) => {
return(
await axios.post(endpoint, data)
.then(res =>{
return res.data.user
})
.catch(error =>{
throw Error(error.response.data)
})
)
}
)
const userSlice = createSlice({
name: 'user',
initialState: initialUserState,
reducers:{
currentUser(state, action){
state.currentUser = action.payload
}
},
extraReducers:
(builder) => {
builder.addCase(getUser.pending, ()=>{
console.log("authing")
})
builder.addCase(getUser.fulfilled, (state, action)=>{
state.currentUser = action.payload
console.log("fulfilled")
})
builder.addCase(getUser.rejected, (state, action)=>{
console.log("failed")
alert(action.error.message)
})
}
})
export const userActions = userSlice.actions;
export default userSlice.reducer;
notificationSlice
import React from 'react'
import { useSelector } from 'react-redux'
function Notification() {
const toast = useSelector(state => state.notification)
console.log(toast)
return (
toast.active &&
<div className="notification" style={{backgroundColor:toast.backgroundColor}} >
{toast.message}
</div>
)
}
export default Notification
I want to change notification state when ever one of the extra reducer in userSlice is called
I think you are thinking about this almost exactly backwards. What you want is NOT to "call notification reducer from userSlice," but to LISTEN for userSlice actions in a notificationSlice.
I have done something like the following, which I think would work well for you:
import { createEntityAdapter, createSlice, isAnyOf } from '#reduxjs/toolkit'
const notificationsAdapter = createEntityAdapter()
const initialState = notificationsAdapter.getInitialState({
error: null,
success: null,
})
const notificationsSlice = createSlice({
name: 'notifications',
initialState,
reducers: {
clearNotifications: state => {
state.error = null
state.success = null
},
setError: (state, action) => {
state.success = null
state.error = action.payload
},
setSuccess: (state, action) => {
state.success = action.payload
state.error = null
},
},
extraReducers: builder => {
builder
.addMatcher(
isAnyOf(
getUser.fulfilled,
),
(state, action) => {
state.error = null
state.success = action.payload.message
}
)
.addMatcher(
isAnyOf(
getUser.rejected
// can add as many imported actions
// as you like to these
),
(state, action) => {
state.error = action?.payload
state.success = null
}
)
// reset all messages on pending
.addMatcher(
isAnyOf(
getUser.pending
),
(state, action) => {
state.error = null
state.success = null
}
)
},
})
export const { clearNotifications, setError, setSuccess } = notificationsSlice.actions
export default notificationsSlice.reducer
export const getErrorMsg = state => state.notifications.error
export const getSuccessMsg = state => state.notifications.success
Having added the above, you can now create a notification component that listens for
const error = useSelector(getErrorMsg)
const success = useSelector(getSuccessMsg)
and shows the messages accordingly.
Caveat:
My notificationSlice code assumes that when an action completes, there will exist a "message" object on the success payload. So, on my async thunks, if my api does not return this I must add this explicitly to the result.