I'm trying to learn redux-toolkit but I struggle a little bit to access my data after set it.
I tried to create a registration form, once the form is filled in I set the values in my store and try to access them through a hook on another page.
However, I only get the initial state and not the values set earlier.
This is my code:
auth.ts
const tokenManager = new TokenManager();
const userManager = new UserManager();
export const auth = createSlice({
name: "auth",
initialState: {
token: "",
user: null
},
reducers: {
get: (state) => state,
setUser: (state, action) => {
userManager.setUser(action.payload);
state.user = action.payload;
},
setToken: (state, action) => {
if (action.payload !== tokenManager.getToken()) {
tokenManager.setToken(action.payload);
}
state.token = action.payload;
}
},
});
export const authActions = auth.actions;
export const authReducer = auth.reducer;
store.ts
export const store = configureStore({
reducer: {
auth: authReducer,
},
});
useUser.ts
export const useUser = () => {
return useSelector((state: any) => state.auth.user);
}
SignupView.tsx
function handleOnSubmit(values: SignupValues) {
dispatch(authActions.setToken("ahohegogogao"));
dispatch(authActions.setUser(values));
navigate("/");
}
When I try to access the value that useUser gives me, I end up with the initial state of my reducer but i don't understand why.
Does anyone have any idea where the problem might come from?
Thanks for your help.
------- EDIT :
Looks like I need to do that :
initialState: {
token: tokenManager.getToken(),
user: userManager.getUser()
},
To be able to retrieve my state when i change the url...
Related
Problem: I want to write some code more efficiently.
Below you will find my full code example of a Redux Toolkit slice
import { createSlice } from '#reduxjs/toolkit';
import { setCookie } from '../../utils/storageHandler';
const initialState = {
name: null,
age: null
}
const formSlice = createSlice({
name: 'formSlice',
initialState: initialState,
reducers: {
setName(state, action) {
state.name = action.payload;
setCookie('name', action.payload);
},
setAge(state, action) {
state.age = action.payload;
setCookie('age', action.payload);
}
}
});
export const { setName, setAge, } =
formSlice.actions;
export default formSlice.reducer;
I do not want to write setCookie(name, value) each time I run a reducer.
Rather I want to write it once and call it each time a reducer function has been called.
I would pass a payload of:
{type: string, value: string}
and then run a callback from every reducer and call the setCookie(...) function as follows:
setCookie(action.payload.type, action.payload.value)
Ideally I would write this code within the slice as so
const formSlice = createSlice({
name: 'formSlice',
initialState: initialState,
reducers: {
setName(state, action) {
state.name = action.payload;
},
setAge(state, action) {
state.age = action.payload;
}
},
callback(action) {
setCookie(action.payload.type, action.payload.value)
}
});
Is there a way of achieving this?
Or maybe an another way of thinking?
My main goal is to have form values stored within cookies, so as to prefill the registration form each time a customer visits it.
There is no authentication.
I will not write cookie storage logic within a component!
Thank you for your attention and I wish you a pleasant day :)
Have you looked into passing a middleware function? You can create your own custom middleware like this
const saveForm = (api: MiddlewareAPI) => (next) => (action) => {
if (action.type === 'setName') { ... }
if (action.type === 'setAge') { ... }
return next(action);
}
and then concat it to the default
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(saveForm),
})
https://redux-toolkit.js.org/api/configureStore#middleware
I am building login system. I am having problem with store state value when I use Selector function. in my system, I want to get isSuccess, error, userInfo from store state values
/login/index.js
const userLogin = useSelector((state) => state.userLogin);
console.log(userLogin);
const { isSuccess, error, userInfo } = userLogin;
if error it will show error message on UI, otherwise if isSuccess it will show success message. isSuccess appears when post to api success (it mean user login success) and user data is dispatched to loginSuccess reduces else error value dispatched to loginFail reduces
/action/userAction.js
import {
loginSuccess,
loginFail,
} from "\~/redux/user/userLoginSlice";
export const login = (inputs) =\> async (dispatch) =\> {
try {
const {data} = await request.post("/auth/login", inputs);
dispatch(loginSuccess(data));
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
console.log(error.response.data)
dispatch(loginFail(error.response.data))
}
. however instead of just getting isSuccess in loginSucces reduce, it also get error when previous login was loginFail
console.log(userLogin). it should only show userInfo and isSuccess. anyone help me to solve this problem with . this is reduces
userLoginSlice.js
import { createSlice } from '#reduxjs/toolkit';
const userLoginSlice = createSlice({
name: 'userInfo',
initialState: {},
reducers: {
loginSuccess: (state, action) => {
state.isSuccess = true;
state.userInfo = action.payload;
},
loginFail: (state, action) => {
state.error = action.payload
},
},
});
export const { loginSuccess, loginFail } = userLoginSlice.actions;
export default userLoginSlice.reducer;
and my reduce store
store.js
import { configureStore } from '#reduxjs/toolkit';
import productModalReducer from './product-modal/productModalSlice';
import cartItemsSlice from './cart/cartSlide';
import userLoginSlice from './user/userLoginSlice.js';
import userRegisterSlice from './user/userRegisterSlice.js';
import userUpdateSlice from './user/userUpdateSlice.js';
const userInfoFromStorage = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
export const store = configureStore({
reducer: {
productModal: productModalReducer,
cartItems: cartItemsSlice,
userLogin: userLoginSlice,
userRegister: userRegisterSlice,
userUpdate: userUpdateSlice,
},
preloadedState: initialState,
});
State value not change when dispatch to another reduces in redux
It seems that this could be because loginSuccess is only adding userInfo and isSuccess to the state but not omitting previous error.
Perhaps try reset state value to exclude error in the reducer:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
state = { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
state = { error: action.payload };
},
},
});
Or return a state value instead of using Immer proxy state:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
return { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
return { error: action.payload };
},
},
});
im dispatch the user from my component to the setUserAsync in my userSlice folder, but when im dispatch it, the user even not came to the createAsyncThunk in the console.log , my goal is to save the user in my state , and in the local storage.
im dispatch the user from my component to the setUserAsync in my userSlice folder, but when im dispatch it, the user even not came to the createAsyncThunk in the console.log , my goal is to save the user in my state , and in the local storage.
userSlice.js
import {createSlice , createAsyncThunk} from '#reduxjs/toolkit'
import {userService} from '../services/user-service'
const initialState = {
user: null,
loading:false
}
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser(state, action) {
state.user = action.payload
},
},
extraReducers: (builder) => {
builder.addCase(setUserAsync.pending, (state) => {
state.user.status = 'loading'
})
.addCase(setUserAsync.fulfilled, (state, action) => {
state.status = 'complete'
state.user = action.payload
})
.addCase(setUserAsync.rejected, (state) => {
state.status = 'failed'
})
}
})
export const setUserAsync = createAsyncThunk(
'user/userservice',
async (loggedInUser) => {
console.log(loggedInUser);
const user = await userService.login(loggedInUser)
return user.data
}
)
export const { setUser } = userSlice.actions
export const selectUser = (state) => state.user.user
export default userSlice.reducer
userService.js
import { storageService } from "./storage-service";
export const userService = {
login
}
const USER_KEY = 'user'
// let loggedInUser
export async function login(user) {
console.log(user);
const newUser = storageService.store(USER_KEY, user)
console.log(newUser);
return new Promise.resolve(newUser)
}
storageService.js
function store(key, value) {
localStorage[key] = JSON.stringify(value);
}
function load(key, defaultValue = null) {
var value = localStorage[key] || defaultValue;
return JSON.parse(value);
}
export const storageService = {
store,
load
}
You should not use createAsyncThunk at all, because localStorage.setItem() is not async: Is HTML5 localStorage asynchronous?
There is also no need to track this operation with a loading flag. You can simply assume that it happens immediately.
What you want to do is:
const initialState = {
// Load previously persisted user or set it to null if none exists.
user: JSON.parse(localStorage.getItem('__MY_REACT_APP_USER__'))
}
And then for the slice:
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser(state, action) {
state.user = action.payload;
localStorage.setItem('__MY_REACT_APP_USER__', JSON.stringify(action.payload));
},
}
});
Why __MY_REACT_APP_USER__ and not just user or USER or USER_KEY? You're risking a name collision by using a common word, other libraries might write to localStorage as well. It's better to obfuscate it a bit.
I'm using react typescript with redux toolkit and in order to get user Info I've got stuck .
this a userSlice.ts:
export const userSlice = createSlice({
name: "user",
initialState: {
user: null,
},
reducers: {
setUser: (state, action) => {
state.user = action.payload;
},
},
});
this is part of protectedRoute.tsx :
export function ProtectedRoute(props: { children: any }) {
const {user} = useSelector((state: any) => state.user);
const dispatch = useDispatch();
const navigate = useNavigate();
const getUser = async () => {
try {
dispatch(showLoading());
const response = await axios.post(
"/api/users/user",
{ token: localStorage.getItem("token") },
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
);
dispatch(hideLoading());
if (response.data.success) {
console.log(response.data.data.isAdmin);
dispatch(setUser(response.data.data));
the problem is state.user can take only one value I want to register all the data from the backend.
How Can I make that
What exactly do u mean by state.user can take only one value ?
If you mean You want to store all the data from the server regarding user you can also turn the user into a type with expected object and pass the whole response object.
i.e
type User = {
isAdmin: boolean,
...
}
initialState: {
user: User,
},
I am trying to render a homepage depending on user being connected or not.
App.js
const user = useSelector(selectUser);
{!user ? <Login></Login> : (
<div className="app__body">
<Sidebar></Sidebar>
<Feed></Feed>
<Widget></Widget>
</div>
)}
userSlice.js
import { createSlice } from '#reduxjs/toolkit';
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;
export const selectUser = (state) => state.user;
export default userSlice.reducer;
Login.js
function Login() {
const dispatch = useDispatch()
const loginToApp = (e) => {
e.preventDefault();
auth.signInWithEmailAndPassword(email, password).then(userAuth => {
dispatch(
login({
email: userAuth.user.email,
uid: userAuth.user.uid,
displayName: userAuth.user.displayName,
photoUrl: userAuth.user.photoURL,
})
)
}).catch(error => alert(error))
}
Though <Login></Login> is always being rendered, even if user is not null, I don't understand as in the redux console, user is not null.
Your screenshot only includes the render part and the problem might not be render, I think. It must be related to the logic. For example, you might defined user as a state and use it.
To get help, I think you should check the part and show them, too.
Well, not really sure about this. I think the problem is here.
export const selectUser = (state) => state.user;
According to official document you need to use createSelector.
So it should try like this.
export const selectUser = createSelector(
(state) => state.user
)
...
const user = useSelector(selectUser);
Ok, my store name was wrong because I've npx created it with redux template and haven't changed store name from counter to user.
import { configureStore } from '#reduxjs/toolkit';
import userReducer from '../features/userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});
It all works now.