I am creating a login and in the .then function, setting the state of user.
The data is set correctly but after redirect (through code) to the next page, I could not access the new state using useSelector function.
Login.js
const handleSubmit = useCallback((e) => {
async function getUser(){
return await login(username, password);
}
e.preventDefault();
getUser()
.then((userObj) => {
if(userObj.user._id !== undefined){
dispatch(setAuthUser(userObj.user));
message.current.innerHTML = '';
window.location = `/Shops/Near/`;
history.push('/Shops/Near/');
}
else{
message.current.innerHTML = 'Invalid username or password, try again!';
}
});
}, [username, password]);
Shops.js
import { useSelector } from 'react-redux';
const Shops = () => {
const [shops, setShops] = useState([]);
const [isPreferedPage, setIsPreferedPage] = useState(false);
const message = useRef(null);
const user = useSelector((state) => {
console.log(state.user);
return state.user;
}); //bug
userSlice.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
};
export const user = createSlice({
name: 'user',
initialState,
reducers: {
setAuthUser: (state, action) => {
state = action.payload;
},
I want to know what is the problem, I tried to remove the async call and even set the state in empty call but still it never been updated.
Thanks
You cannot do state = as that would not modify the object in state, but throw the object in state away and put a new one into the variable.
state.foo = would be okay, state = is not.
Do return action.payload instead.
See also https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state
Related
I have a problem, I'm looking for a solution, please help.
I am building a React application and using Redux thunk to call the data from the backend
This is the code inside the useEffect
let [events, setEvents] = useState([]);
let [featured, setFeatured] = useState([]);
let [categories, setCategories] = useState([]);
let [interests, setInterests] = useState([]);
useEffect(() => {
let params = new FormData();
if (user) {
params.append('phone_number', user.phone_num);
}
dispatch(
getHomeData(params, json => {
if (json.success) {
console.log(data);
let {data} = json;
setEvents(data?.events);
setFeatured(data?.featured);
setCategories(data?.categories);
setInterests(data?.interests);
if (user) {
dispatch(setNotificationCount(data?.notifications));
}
}
}),
);
}, []);
redux action
import {AppConstant, httpHelperApp} from '../../common';
import {
NOTIFICATION_SET,
HOME_DATA_PENDING,
HOME_DATA_FULFILLED,
HOME_DATA_REJECTED,
} from '../constant';
let HomeApi = 'api/adjusted/essentials.php';
export let getHomeData = (payload, callBack) => {
return async dispatch => {
dispatch({type: HOME_DATA_PENDING});
let data = await httpHelperApp.postApi(payload, HomeApi);
if (data.success) {
dispatch({type: HOME_DATA_FULFILLED, payload: data});
} else {
dispatch({type: HOME_DATA_REJECTED});
}
};
};
export let setNotificationCount = payload => {
return {
type: NOTIFICATION_SET,
payload,
};
};
redux-reducer
let initialState = {
notificationCount: 0,
};
export const HomeReducer = (state = initialState, action) => {
const {type, payload} = action;
switch (type) {
case NOTIFICATION_SET:
return {...state, notificationCount: payload};
default:
return state;
}
};
The problem is that I get an infinite loop and the useEffect keeps working but it stops when I stop the dispatch notification dispatch(setNotificationCount(data?.notifications)) or stop any update to state redux.what did I do wrong please help.
I'm creating a game that user can login with just a username as a guest and play. Normally, when user visits the website I'm fetching all users data from firebase and assigning it to users state and then if user click Play button I'm checking if username exist.
Where I create function to fetch users using firebase
import { async } from "#firebase/util";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { collection, query, where, getDocs } from "firebase/firestore";
import { db } from '../firebase/auth';
export interface User{
isUser: boolean;
id: number;
username: string
}
interface UserState{
users:User[];
isLoading:boolean;
errorMessage:string;
}
export const fetchUsers = createAsyncThunk("users/fetchUsers", async()=>{
const usersArray:User[] = []
const usersRef = collection(db, "users");
const q = query(usersRef, where("isUser", "==", true))
const querySnapshot = await getDocs(q);
querySnapshot.forEach(doc =>{
const user = doc.data() as User;
usersArray.push(user);
})
return usersArray;
})
const initialState: UserState = {
users:[],
isLoading:false,
errorMessage:""
}
const usersSlice = createSlice({
name:"users",
initialState,
reducers:{},
extraReducers:(builder)=>{
builder.addCase(fetchUsers.pending, (state, action)=>{
state.isLoading = true;
})
builder.addCase(fetchUsers.fulfilled, (state, action)=>{
state.isLoading = false;
state.users = action.payload;
state.errorMessage = "";
})
builder.addCase(fetchUsers.rejected, (state, action)=>{
state.isLoading = false;
state.users = [];
if(typeof action.error.message === "string" ){
state.errorMessage = action.error.message
}
})
}
})
export const usersActions = usersSlice.actions;
export const userReducer = usersSlice.reducer
where I fetch all data
useEffect(() => {
dispatch(guestInputActions.changeInputValue(randomUser))
dispatch(fetchUsers())
}, [])
Where I check if user exsits
const handleGuestLogin = () => {
async function checkIfUsernameExist() {
if (users) {
const existUser = users.find((user: User) => user.username === inputValue.toLowerCase())
if (existUser) {
dispatch(userLoginActions.setLoginStatus(false))
dispatch(userLoginActions.setErrorMessage("This username already exists!"))
return
} else {
dispatch(userLoginActions.checkUserName(inputValue))
}
}
}
checkIfUsernameExist();
}
But I don't think it is efficent way. I want to fetch all users data when user click play button not on initial render. But when I try to do that with async/await my users array always empty before I check if username exists. So, user can play game even username is in already database
What I tried to fetch users data when user click Play button (not on initial render)
useEffect(() => {
dispatch(guestInputActions.changeInputValue(randomUser))
// dispatch(fetchUsers()) commented out this line
}, [randomUser])
const handleGuestLogin = async () => {
async function checkIfUsernameExist() {
if (users) {
const existUser = users.find((user: User) => user.username === inputValue.toLowerCase())
if (existUser) {
dispatch(userLoginActions.setLoginStatus(false))
dispatch(userLoginActions.setErrorMessage("This username already exists!"))
return
} else {
dispatch(userLoginActions.checkUserName(inputValue))
}
}
}
const val = await dispatch(fetchUsers());
checkIfUsernameExist();
}
How to update the state just after dispatch?
State should be updated but is not.
What should be changed? Even when we will use then in our code -> even then we will not receive updated state, only when we will take value from the like .then((value) => { value.data }), but I want to take data from the state
Slice Code:
const authSlice = createSlice({
name: 'auth',
initialState: {
user: {},
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(activateUser.fulfilled, (state, action) => {
state.user = action.payload.userData
})
},
})
export const activateUser = createAsyncThunk('auth/activate', async (data) => {
try {
const userData = await authService.activateAccount(data)
return { userData: userData.data.data }
} catch (error) {
const message =
(error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString()
return message
}
})
const { reducer } = authSlice
export default reducer
Component:
function ActivateAccount() {
const { user } = useSelector((state) => state.auth)
const [code, setCode] = useState('')
const dispatch = useDispatch()
const activateUserAccount = () => {
const data = {
code: code?.value,
userId: user?._id,
email: user?.email || email?.value,
}
dispatch(activateUser(data))
console.log('Why here value is not updated yet?', user)
if (!user.activated) {
setCode({
...code,
isNotActivated: true,
})
return
}
return navigate('/on-board')
}
}
Why in the console log value is not yet updated?
What should be changed?
Any ideas?
Even though it's Redux it still needs to work within the confines of the React component lifecycle. In other words, the state needs to be updated and subscribers notified and React to rerender with the updated state. You are currently logging the user state closed over in function scope of the current render cycle. There's no possible way to log what the state will be on any subsequent render cycle.
You can chain from the asynchronous action though, and check any resolved values.
function ActivateAccount() {
const { user } = useSelector((state) => state.auth);
const [code, setCode] = useState('');
const dispatch = useDispatch();
const activateUserAccount = () => {
const data = {
code: code?.value,
userId: user?._id,
email: user?.email || email?.value,
}
dispatch(activateUser(data))
.unwrap()
.then(user => {
console.log(user);
if (!user.activated) {
setCode(code => ({
...code,
isNotActivated: true,
}));
return;
}
return navigate('/on-board');
});
}
...
}
I am using Redux for state management, but I encountered a problem. My issue is I like to set state only if state is different. Let me clarify my problem through my code.
// MyComponent.jsx
const [query, setQuery] = useState('');
useEffect(() => {
if(query.length) {
let {search, cancel} = searchContent(query);
search.then(res =>
setSearchResult(res.data)
).catch(e => {
if(axios.isCancel(e)){
return;
}
})
return () => cancel();
}else{
setSearchResult(null);
}
}, [query, setSearchResult])
Above is my component that is supposed to set search state.
// action.js
export const SET_SEARCH_RESULT = 'SET_SEARCH_RESULT';
export const setSearchResult = (val) => ({
type: SET_SEARCH_RESULT,
searchResult: val,
});
//reducer.js
import { SET_SEARCH_RESULT } from './article.action';
const INITIAL_STATE = {
searchResult: null
}
const articleReducer = (state=INITIAL_STATE, action) => {
switch (action.type) {
case SET_SEARCH_RESULT:
return {
...state,
searchResult: action.searchResult
}
default:
return state
}
}
I am able to set state using redux and it works fine. However, my problem is even though initial state is null, when useEffect function runs initially my state sets to null.
My question is how can I use redux so that only it runs if state is different.
Thanks in advance.
Hello I am fairly new to React, Redux and Saga. So I have a scenario where I have a .jsx file which is the view file then an action file used for dispatch and I am also using saga which updates the data in the reducers. Following are the file structurs:
Action file:
export const getAction = (requestor) => ({
type: GET_ACTION,
data: {
requestor,
},
});
Reducer file
export const Reducer = (currentState = {}, action) => {
const newState = { ...currentState };
switch (action.type) {
case GET_ACTION:
newState.data = action.data;
return newState;
}
};
Saga file
function* getData(action) {
const { requestor } = action.data;
try {
const data = yield call(callService);
if(success) {
yield put( {type: GET_ACTION, data} );
}
} catch (e)
{
}
}
function* getDataSaga() {
yield takeLatest(GET_ACTION, getData);
}
export {
getData,
};
export default [
getDataSaga,
];
jsx file
const [dummy, setDummy] = useState([]);
const data = useSelector(state => state.data, shallowEqual) || {};
There is a function in which dispatch function is called.
dispatch(getAction(requestor));
Now I need to access the updated state of data after dispatch has finished updating the data because after the data is updated I have to setDummy to set the dummy variable mentioned. Any way which I can be approaching to achieve that. I have tried to use dispatch.then but on UI it is saying .then is not a function for dispatch.
after the data is updated I have to setDummy
useEffect lets you do something upon a given prop changing
const [dummy, setDummy] = useState([]);
const data = useSelector(state => state.data, shallowEqual) || {};
// setDummy when `data` changes
useEffect(() => {
setDummy(data);
}, [data])