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.
Related
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’ve been working on a react crud application to teach myself redux toolkit, and I can’t seem to get a particular async function to work. I’m trying to fetch recipes from a certain user from firebase - this function worked when I was just playing around and didn’t have any users or authentication (i.e., the database was just recipes), but now that I have authentication and users, I cannot fetch or display the recipes, and the recipes are not added to the recipes state array. Thanks to redux logger, I can see that the recipes are accessed from firebase, but they're not displayed or added to the recipes state. Once I refresh the page, the application breaks. I can add and delete recipes to a user in firebase, though.
Here’s what the async thunk looks like:
'user/getRecipes',
async(uid, thunkAPI) => {
const snapshot = await getDocs(collection(db, `users/${uid}/recipes`))
const array = []
snapshot.forEach((doc) => {
array.push(doc.data())
})
return array
}
)
Here’s where I call it inside a useEffect. I’ve tried using the uid as an argument and getting the uid with getState() inside the thunk - it doesn’t seem to make a difference but maybe I’m doing that wrong, as well?
const RecipeApp = () => {
const dispatch = useDispatch()
const recipes = useSelector((state) => state.recipes)
const auth = getAuth()
const navigate = useNavigate()
const [user, loading, error] = useAuthState(auth)
const uid = useSelector(state => state.users.uid.user)
useEffect(() => {
if (loading) return;
if (!user) return navigate("/");
}, [user, loading])
useEffect(() => {
dispatch(getRecipes(uid))
console.log(uid)
}, [dispatch])
const addRecipe = (name, ingredients, instructions, notes) => {
const date = new Date()
const newRecipe = {
name: name,
ingredients: ingredients,
instructions: instructions,
notes: notes,
recipeId: uuidv4(),
date: date.toLocaleDateString(),
createdAt: Date.now(),
}
dispatch(addRecipeToFirestoreAndRedux(newRecipe))
}
return (
<div className="recipeapp">
<h1 className='recipeapp__heading'>Recipes</h1><button onClick={logout}>Log out</button>
<div className="recipeapp__container">
<ErrorBoundary
FallbackComponent={ErrorFallBack}>
<RecipeList
recipes={recipes}
/>
</ErrorBoundary>
<AddRecipe
addRecipe={addRecipe}
/>
</div>
</div>
);
}
export default RecipeApp;
Here’s what the relevant code (and a little more) of the recipes reducer looks like:
export const recipeSlice = createSlice({
name: 'recipesSlice',
initialState: {
recipes: []
},
reducers: {
ADD_RECIPE: (state, action) => {
state.recipes.push(action.payload)
},
DELETE_RECIPE: (state, action) => {
state.recipes = state.recipes.filter((recipe) => recipe.recipeId !== action.payload.recipeId)
},
extraReducers: builder => {
builder.addCase(getRecipes.fulfilled, (state, action) => {
state.recipes = action.payload
})
}
})
export const { ADD_RECIPE, DELETE_RECIPE } = recipeSlice.actions;
export default recipeSlice.reducer
And here's what the store.js file looks like:
import { configureStore, combineReducers } from "#reduxjs/toolkit";
import recipeReducer from './features/recipeslice'
import userReducer from './features/authentication'
import thunk from "redux-thunk";
import logger from "redux-logger";
const rootReducer = combineReducers({
recipes: recipeReducer,
users: userReducer
})
export const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger]
})
Apologies if this is overkill. Hopefully this makes sense, and thank you in advance for any and all help!
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...
in below react/ redux toolkit app , in userslice file I am trying to export my entities piece of state and import in main file , when I try to console in comes undefined , not sure why its undefined ,
but When I trying to pull the {entities} directly form state its working fine, would like to know why its showing undefined in console, if anyone knows please check ?
below is the state part which I am getting undefined
export const { SelectUserList } = (state) => state.userslist.entities;
below is the console which shows undefiend
console.log(SelectUserList);
my slice file is below
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export const fetchuserlist = createAsyncThunk(
"userslist/fetchusers",
async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
}
);
const userSlice = createSlice({
name: "userslist",
initialState: {
entities: [],
loading: false,
},
reducers: {
// userAdded(state, action) {
// state.entities.push(action.payload);
// },
},
extraReducers: {
[fetchuserlist.pending]: (state, action) => {
state.loading = true;
},
[fetchuserlist.fulfilled]: (state, action) => {
state.entities = [...state.entities, ...action.payload];
state.loading = false;
},
[fetchuserlist.rejected]: (state, action) => {
state.loading = false;
},
},
});
export const { userAdded, userUpdated, userDeleted } = userSlice.actions;
export const { SelectUserList } = (state) => state.userslist.entities;
export default userSlice.reducer;
me component file is below
import React from "react";
import { fetchuserlist, SelectUserList } from "./features/userSlice";
import { useDispatch, useSelector } from "react-redux";
const Mainlist = () => {
const dispatch = useDispatch();
const { entities } = useSelector((state) => state.users);
console.log(SelectUserList);
return (
<div>
<button onClick={() => dispatch(fetchuserlist())}>Load list</button>
{entities?.map((s) => (
<div className="user_list" key={s.id}>
<h4>{s.id}</h4>
<h6>{s.email}</h6>
<button>delete</button>
<button>edit</button>
</div>
))}
</div>
);
};
export default Mainlist;
In your slice you are declaring the function in the wrong way. You should declare the SelectUserList function like this:
export const SelectUserList = (state) => state.userslist.entities;
In your component file you should access the entities returned in SelectUserList with a useSelector. Like this:
const usersListEntities = useSelector(SelectUserList);
I'm new to react-redux and react-navigation.
I'm trying to build a simple app with a TextInput in the MainScreen that the user types his username and when he press "Login" that username will be saved up in the redux store and the navigation will navigate automatically to the "HomeScreen".
App.js:
const initialState = { username: "" };
const reducer = (state = initialState, action) => {
switch (action.type) {
case "STORE_USERNAME":
return { username: state.username };
default:
return state;
}
};
const store = createStore(reducer);
MainScreen.jsx:
const mapStateToProps = state => {
return {
username: state.username
};
};
const mapDispatchToProps = dispatch => {
return {
storeUsername: () => {
dispatch({ type: "STORE_USERNAME" });
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Main);
What should I change or add?
First you need to create an appNavigator from react-navigation like this :
import {createAppContainer, createSwitchNavigator} from 'react-navigation';
import {createStackNavigator} from 'react-navigation-stack';
const AppStack = createStackNavigator({
HomeScreen: {
screen: Home,
},
Login: {
screen: Login,
}});
const navigation = createAppContainer(AppStack);
And suppose what you are doing by storing username in redux state is correct, just now you need to navigate it.
SO suppose in Login.js
onButtonPress = () => {
this.props.storeUsername();
this.props.navigation.navigate('Home');// here you are redirecting to the main page after username has been stored.
}
render(){
return(
// your other code
<Button onPress={this.onButtonPress} title="submit username">
)
}
const mapStateToProps = state => {
return {
username: state.username
};
};
const mapDispatchToProps = dispatch => {
return {
storeUsername: () => {
dispatch({ type: "STORE_USERNAME" });
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Hope it helps. feel free for doubts