Please help me with a situation around react-redux toolkit. I have issues in updating the state ( which i try to do in a immutable way ), and the component where I use it, never rerenders.
export const updateUser = createAsyncThunk("users/updateUser", async ({ id, name, username }) => {
const response = await axios.patch(`https://jsonplaceholder.typicode.com/users/${id}`,{
name,
username
});
return response.data;
});
const userEntity = createEntityAdapter({
selectId: (user) => user.id
})
const userSlice = createSlice({
name: "user",
initialState: userEntity.getInitialState(),
extraReducers: {
[getUsers.fulfilled]: (state, action) => {
userEntity.setAll(state, action.payload);
},
[updateUser.fulfilled]: (state, action) => {
userEntity.updateOne(state, { id: action.payload.id, update: action.payload});
}
},
});
export const userSelectors = userEntity.getSelectors(state => state.user)
export default userSlice.reducer;
const EditUser = () => {
const [name, setName] = useState('');
const [username, setUserName] = useState('');
const dispatch = useDispatch();
const navigate = useNavigate();
const { id } = useParams();
const user = useSelector((state) => userSelectors.selectById(state, id));
useEffect(() => {
dispatch(getUsers());
},[dispatch]);
useEffect(() => {
if(user){
setName(user.name);
setUserName(user.username);
}
},[user]);
const handleUpdate = async (e) => {
e.preventDefault();
await dispatch(updateUser({id, name, username}));
navigate('/user');
}
whenever I click on button and update the state. it updates on redux. but state does not update. What am i doing wrong?
Image Explanation 1
Image Explanation 2
Image Explanation 3
Related
im dispaching my id to the productsSlice , and from there im call to getProductById() that in my productService, the productService communicate with async-storage-service and i think the problem comes from here.
i want to get the product from the id i sent.
i dont have an error in the console.
//ProductDetails.jsx
export function ProductDetails() {
let [loading, setLoading] = useState(true);
const dispatch = useDispatch()
// let [color, setColor] = useState("#ffffff");
const { id } = useParams();
console.log(id);
const [product, setProduct] = useState(null);
useEffect(() => {
dispatch(getProduct(id))
}, [id, product]);
//productsSlice.js
const productsSlice = createSlice({
name: 'products',
initialState,
reducers: {
getProduct: (state, action) => {
productsService.getProductById(action.payload)
}
},
//products-service.js
async function query() {
const response = await axios.get('https://dummyjson.com/products')
const products = await response.data
localStorage.setItem(PRODUCTS_KEY, JSON.stringify(products.products))
return products
}
async function getProductById(id) {
return storageService.get(PRODUCTS_KEY, id)
}
//async-storage-service.js
export const storageService = {
query,
get,
post,
put,
remove,
postMany
}
function query(entityType) {
var entities = JSON.parse(localStorage.getItem(entityType)) || []
return Promise.resolve(entities);
}
function get(entityType, entityId) {
return query(entityType)
.then(entities => entities.find(entity => entity.id === entityId))
}
Redux reducer functions are to be considered pure, synchronous functions. Your getProduct action is calling asynchronous code and it doesn't update any state.
Convert getProduct to an asynchronous action and specify an extraReducers reducer function to handle the fulfilled Promise returned by it.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
const getProduct = createAsyncThunk(
"/products/getProduct",
async (id, thunkAPI) => {
return productsService.getProductById(id);
},
);
const productsSlice = createSlice({
name: 'products',
initialState,
reducers: {
...
},
extraReducers: builder => {
builder
.addCase(getProduct.fulfilled, (state, action) => {
state.product = action.payload; // * Note
});
},
};
*Note: You didn't include what initialState is so we've no way of knowing what state should actually be updated, but it's just a normal Redux state update from here. Tweak this part to fit your actual use case.
Hello I am making login app used firebase google login. I want to save the user in the user-reducer.
The console shows user info from the firebase! but, It keeps shows undefined. I don't know why it shows like these.
Please advised!
[user-action ]
import { UserType } from './user.types';
export const login = ({ currentUser }) => ({
type: UserType.USER_LOGIN,
payload: currentUser,
});
[user-types ]
export const UserType = {
USER_LOGIN: 'USER_LOGIN',
};
[user-reducer]
import { UserType } from './user.types';
const INITAIL_STATE = {
user: [],
};
const userReducer = (state = INITAIL_STATE, action) => {
switch (action.type) {
case UserType.USER_LOGIN:
return {
...state,
user: action.payload,
};
default:
return state;
}
};
export default userReducer;
[Login.js]
const Login = ({ googleLogin }) => {
const [currentUser, setCurrentUser] = useState(null);
const getUser = new Promise((resolve, reject) => {
console.log('doing...');
firebase.auth().onAuthStateChanged(setCurrentUser);
resolve(currentUser);
console.log(currentUser);
});
getUser
.then(user => {
googleLogin(user);
})
.catch(error => {
console.log(error);
});
....
const mapDispatchToProps = dispatch => ({
onModal: () => dispatch(modalHandler()),
googleLogin: currentUser => dispatch(login(currentUser)),
});
export default connect(null, mapDispatchToProps)(Login);
Your problem is on getUser, you are resolving promise passing a state variable (currentUser) that may have been not setted (because setCurrentUser is async). I would suggest to modify you code in this way:
const getUser = new Promise((resolve, reject) => {
console.log('doing...');
firebase.auth().onAuthStateChanged(firebaseUser => {
setCurrentUser(firebaseUser);
resolve(firebaseUser);
console.log(firebaseUser);
});
});
export const login = ({ currentUser }) => ({
type: UserType.USER_LOGIN,
payload: currentUser,
});
should be -
export const login = (currentUser) => ({
type: UserType.USER_LOGIN,
payload: currentUser,
});
i.e no destructuring of currentUser
OR
googleLogin: currentUser => dispatch(login(currentUser))
should be
googleLogin: currentUser => dispatch(login({currentUser}))
I need to render a component that has a route using react router. the first component has a button that when clicked needs to render another component that has state passed in from the first component. The page redirects but doesn't load. All of the data from the first component I want is passed in but it wont set state when I use setProfile(p). All the other console.log()s in the member component show all the data I expect but it won't set the state with this data.
import {useLocation} from "react-router-dom";
const Member = (props)=> {
const [user, setUser] = useState({});
const [profile, setProfile] = useState({});
const [user, setUser] = useState({});
const { state } = useLocation();
const [profile, setProfile] = useState({});
const dispatch = useDispatch();
const [list, setList] = useState([]);
const [posts, setPosts] = useState([]);
const [snInstance, setsnInstance] = useState({});
// run effect when user state updates
useEffect(() => {
const doEffects = async () => {
try {
// const p = await incidentsInstance.usersProfile(state.user, { from: accounts[0] });
// const a = await snInstance.getUsersPosts(state.user, { from: accounts[0] });
if (state && state.user) {
setUser(state.user);
}
const accounts = await MyWeb3.getInstance().getAccounts();
setAccounts(accounts);
console.log(accounts)
const incidents = MyWeb3.getInstance().getContract(Incidents)
const incidentsInstance = await MyWeb3.getInstance().deployContract(incidents);
const sn = MyWeb3.getInstance().getContract(SocialNet)
const snInstance = await MyWeb3.getInstance().deployContract(sn);
setsnInstance(snInstance);
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
const a = await snInstance.getUsersPosts(pro, { from: accounts[0] });
console.log(a)
console.log(p)
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile, state]);
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div class="container">
<a target="_blank">Name : {profile.name}</a>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
{p.message}
</tr>})}
</div>
)
}
export default Member;
This is the parent component I want to redirect from
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
I have this component working when I don't have a dynamic route that needs data passing in from the parent component It's redirecting from.
My routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
This is the reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
It doesn't make any sense that you would dispatch({ type: 'ADD_MEMBER', response: member }) with a member object that came from the publisher property of a post. That info is already in your state. You probably need to be normalizing your state better so that you can select it where you need it.
You want to use the Link component from react-router-dom to navigate to a member's profile page. Your Route should render the correct profile based on an id or username property in the URL. Don't pass through the data when you redirect, just go to the correct URL. On that Member page you can get the user from the state by looking up the id.
In Posts:
<Link to={`/member/${p.publisher.id}`}><button>Profile</button></Link>
In Routes:
<Route path="/member/:id" component={Member} />
In Member:
const Member = () => {
const { id } = useParams();
const profile = useSelector((state) =>
state.socialNetworkContract.members.find((user) => user.id === id)
);
const dispatch = useDispatch();
useEffect(() => {
const doEffects = async () => {
if ( ! profile ) {
dispatch(loadUser(id));
}
};
doEffects();
}, [dispatch, profile, id]);
So I'm using Auth0 for my user sign up. I'm trying to get the user id under sub:value to add to my database to identify with the post of a user. I'm trying to use a Context API in order to get the user info to put in my database.
react-auth0-spa.js
// src/react-auth0-spa.js
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (window.location.search.includes("code=") &&
window.location.search.includes("state=")) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallback(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await auth0Client.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await auth0Client.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
other.js (trying to get user info from react-auth0-spa.js)
class AddAlbum extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
let value = this.context;
console.log(value);
}
render() {
return (
)
}
AddAlbum.contextType = Auth0Context;
This gives me user: undefined
In my index.js I have this
ReactDOM.render(
<Auth0Provider
domain={config.domain}
client_id={config.clientId}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
<App />
</Auth0Provider>,
document.getElementById("root")
);
Which I believe is giving me these results:
So I'm wondering why my Context API isn't working and giving me user: undefined.
You're logging the user when the component first mounts, which is long before the await auth0FromHook.getUser() call will complete. Log it in a componentDidUpdate, or check in a parent if that value is available, and don't mount the child component until it is.
I'm trying to use Redux via hooks but the state keeps coming back with an empty array rather than the data from the fetch request.
Actions
export const loading = payload => {
return {
type: types.LOADING,
payload
}
}
export const getBudget = payload => {
return {
type: types.BUDGET_DATA,
payload
}
}
export const budgetData = () => {
return dispatch => {
dispatch(loading(true))
const url = `${URL_BUDGET}`
fetch(url)
.then(response => dispatch(getBudget(response.data)))
.catch(err => console.log(err))
dispatch(loading(false))
}
}
Reducer
import * as types from '../types'
const initialState = {
budget: []
}
export default (state = initialState, action) => {
switch (action.types) {
case types.BUDGET_DATA:
return {
...state,
budget: action.payload
}
default:
return state
}
}
Component
const Home = () => {
useDispatch(budgetData(), categoryData())
const state = useSelector(state => state.data)
const budgets = useSelector(state => state.data.budget)
const categories = useSelector(state => state.data.category)
//console.log(this.props.dataReducer)
return (
<div>
content
</div>
)
}
export default Home
I can't seem to understand why the fetch request isn't fulfilled.
My API has the following format of data...
{"meta":{},"data":{"example":[{"timestamp":28378545,"value":5}],...}}
Is there an issue with dispatching?! Adding loading hasn't helped either!
useDispatch returns a dispatch function that subsequently needs to be called. If you want to do this just one time when the component is first rendered, you can pair it with a useEffect that has no dependencies:
const Home = () => {
const dispatch = useDispatch()
const budgets = useSelector(state => state.data.budget)
const categories = useSelector(state => state.data.category)
useEffect(() => {
dispatch(budgetData())
dispatch(categoryData())
}, [])
return (
<div>
content
</div>
)
}
export default Home