useState won't update the state when I set it in react - javascript

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]);

Related

How to call a React Hook from a child component to refresh results

I'm trying to refresh the results on the page, but the refresh button is in a child component to where my React Hook is originally called.
export const ParentComponent = ({
}) => {
const infoINeed = useSelector(getInfoINeed);
const { error, isLoading, data } = useMyAwesomeHook(infoINeed.name);
return (
<div>
<Header/>
<Body className={classes.body}>
<div>Hello Stack overflow</div>
<Body>
</div>
);
};
My Awesome hook looks like this
export const useDogCounts = (name: string | undefined) => {
const { data: token, error: authError } = useAuthHook();
const [error, setError] = useState<Error | null>(null);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
const fetchMyData = async () => {
const request = myRequest(name);
try {
setLoading(true);
const counts = callMyFunction()
setMyData(counts);
setLoading(false);
} catch (requestError) {
if (requestError === null) {
setError(requestError);
} else {
throw requestError;
}
setLoading(false);
}
};
fetchMyData();
}, [name, token]);
return {
data: dogCounts,
error,
isLoading,
};
};
then in my <Header/> component, I have a refresh button that I want to call the hook.
import React, { FC } from 'react';
import { Button } from '#material-ui/core';
export const Header: FC<HeaderProps> = ({}) => {
return (
<Page.Header className={classes.headerWrapper}>
<Page.Title>Dog Counts</Page.Title>
<Button
onClick={() => {}} // functionality to go here
>
Refresh
</Button>
</Header>
);
};
I've tried a couple approaches, including passing a variable into the useDogCount hook called refresh, which the Header component changes in the state in order to trigger the useEffect hook in my main hook. It seemed a bit messy to do it this way and introduce a new variable to keep track of.
I also have implemented something like this elsewhere a different time where I did not use a useEffect hook inside my custom hook, and instead passed the Promise back to the required place to refresh it. However, I need the useEffect hook here to check for updating name or token.
You can return the function used to fetch the data from your custom hook :
export const ParentComponent = () => {
const infoINeed = useSelector(getInfoINeed);
const { error, isLoading, data, fetchData } = useMyAwesomeHook(infoINeed.name);
return (
<div>
<Header onClickRefresh={fetchData}/>
<Body className={classes.body}>
<div>Hello Stack overflow</div>
<Body>
</div>
);
};
export const useDogCounts = (name: string | undefined) => {
const { data: token, error: authError } = useAuthHook();
const [error, setError] = useState<Error | null>(null);
const [isLoading, setLoading] = useState(true);
const fetchData = useCallback(async () => {
... // code to fetch the data
}, [name, token]);
useEffect(fetchData, [fetchData]);
return {
data: dogCounts,
fetchData,
error,
isLoading,
};
};
export const Header: FC<HeaderProps> = ({onClickRefresh}) => {
return (
<Page.Header className={classes.headerWrapper}>
<Page.Title>Dog Counts</Page.Title>
<Button onClick={onClickRefresh}>
Refresh
</Button>
</Header>
);
};
Right now there is no connection between your hook and either of components in terms of firing the request for the data. What I would suggest is to add a function to your hook that is going to call your api and return that function from the hook
export const useDogCounts = (name: string | undefined) => {
const { data: token, error: authError } = useAuthHook();
const [error, setError] = useState<Error | null>(null);
const [isLoading, setLoading] = useState(true);
const callAnApi = async () => {
// ... body of the useEffect
}
useEffect(() => {
const fetchMyData = async () => {
const request = myRequest(name);
try {
setLoading(true);
const counts = callMyFunction()
setMyData(counts);
setLoading(false);
} catch (requestError) {
if (requestError === null) {
setError(requestError);
} else {
throw requestError;
}
setLoading(false);
}
};
fetchMyData();
}, [name, token]);
return {
data: dogCounts,
error,
isLoading,
};
};
then in your ParentComponent you can destructure it as
const { error, isLoading, data, callAnApi } = useMyAwesomeHook(infoINeed.name);
and pass it to Header component as prop where you just use it as
<Button
onClick={callAnApiHandler}
>
Refresh
</Button>
Then you could call this new function inside your useEffect for further refactor

Redux Toolkit State issue when sending to child component

I am creating react redux application using redux toolkit and I'm passing some props to child component, it supposed to be one post because I'm using a map in parent component and passing one data to each component.
I'm trying to do Edit button and when clicking the "Edit button" trying to send ID to redux store but there is an error. If anyone know the answer please let me know.
Below is my redux slice:
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
allPosts: [],
loading: "idle",
error: "",
currentId: "",
};
export const fetchAlltAsync = createAsyncThunk(
"allposts",
async (_, thunkAPI) => {
try {
const response = await axios.get("http://localhost:5000/posts/");
// The value we return becomes the `fulfilled` action payload
return response.data;
} catch (error) {
throw thunkAPI.rejectWithValue({ error: error.message });
}
}
);
export const postsingleAsync = createAsyncThunk(
"postsingleAsync",
async (post, { dispatch }) => {
const response = await axios.post("http://localhost:5000/posts/", post);
return response.data;
}
);
export const idsingleAsync = createAsyncThunk(
"idsingleAsync",
async (id, updatedpost) => {
const response = await axios.patch(
`http://localhost:5000/posts/${id}`,
updatedpost
);
return response.data;
}
);
export const postSlice = createSlice({
name: "posts",
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
// Use the PayloadAction type to declare the contents of `action.payload`
newsetcurrentId: (state, action) => {
state.currentId = action.payload;
},
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder.addCase(fetchAlltAsync.pending, (state) => {
state.allPosts = [];
state.loading = "Loading";
});
builder.addCase(fetchAlltAsync.fulfilled, (state, action) => {
state.allPosts = action.payload;
state.error += "Loaded";
});
builder.addCase(fetchAlltAsync.rejected, (state, action) => {
state.allposts = "data not loaded";
state.loading = "error";
state.error = action.error.message;
});
builder.addCase(idsingleAsync.fulfilled, (state, action) => {
state.currentId = action.payload;
});
},
});
export const { setcurrentId, newsetcurrentId } = postSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state) => state.counter.value;
// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd = (amount) => (dispatch, getState) => {};
export default postSlice.reducer;
Below is my parent component:
import React, { useEffect, useState } from "react";
import Post from "./Post";
import { useSelector, useDispatch } from "react-redux";
const Posts = ({ SETCURRENTID, CURENTID }) => {
// const dispatch = useDispatch();
const posts = useSelector((state) => state.posts.allPosts);
return (
<div>
{posts &&
posts.map(({ _id, ...rest }) => (
<Post key={_id} rest={rest} id={_id} />
))}
</div>
);
};
export default Posts;
This is my child component:
import React from "react";
import moment from "moment";
import { idsingleAsync, newsetcurrentId } from "../../features/postSlice";
import { useSelector, useDispatch } from "react-redux";
const Post = ({ rest, _id }) => {
const dispatch = useDispatch();
console.log(rest, "gff");
//const { id } = this.rest._id;
const handleClick = () => dispatch(newsetcurrentId());
return (
<div>
<h1>{rest.title}</h1>
<img
style={{ maxWidth: "250px", border: "12px solid purple" }}
alt="d"
src={rest.selectedFile}
/>
<h2>{moment(rest.createdAt).fromNow()}</h2>
<button onClick={() => dispatch(newsetcurrentId(rest._id))}> edit</button>
<h5>{rest.tags.map((tag) => `#${tag} `)}</h5>
<h5 onClick={() => {}}>{rest.likeCount}</h5>
<button onClick={() => {}}>Delete</button>
</div>
);
};
export default Post;
This is the redux error:
requestId(pin):undefined
TL;DR
Instead of rest._id , try passing the id prop to your newsetcurrentId dispatch:
const Post = ({ rest, id }) => { //Change _id to id
const dispatch = useDispatch();
const handleClick = () => dispatch(newsetcurrentId());
return (
<div>
<h1>{rest.title}</h1>
<img
style={{ maxWidth: "250px", border: "12px solid purple" }}
alt="d"
src={rest.selectedFile}
/>
<h2>{moment(rest.createdAt).fromNow()}</h2>
{/* pass id here */}
<button onClick={() => dispatch(newsetcurrentId(id))}> edit</button>
<h5>{rest.tags.map((tag) => `#${tag} `)}</h5>
<h5 onClick={() => {}}>{rest.likeCount}</h5>
<button onClick={() => {}}>Delete</button>
</div>
);
};
Explanation
When you are doing this destructuring:
posts.map(({ _id, ...rest }) => ( your rest object will actually contain all the post properties apart from _id so you don't actually have rest._id which you are trying to access on your Post child.
Additionally, you are passing id={_id} as a prop from the parent to the child, so you don't actually have an _id prop on your Post component (change it to id).

Why is the array in my redux reducer not available from another component after a redirect to another page of my app?

I have two separate components. I want to have a button that when clicked on will add an element to an array in my reducer and redirect to another component, this component that gets redirected to needs to render the data that was just added to the array. The page redirects to the component I want but the data does not load and the console.logs don't show anything.
This is the component that has the redirect button. On this component the console.log(socialNetworkContract.members[0]) shows the string I expect.
const Posts = () => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
window.location.href='/member'
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
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 Posts;
This is my 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
}
};
and this is the component that is redirected to. this just says undefined in console.log(socialNetworkContract.members[0])
const Member = () => {
const [user, setUser] = useState({});
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
useEffect(async()=>{
try {
const pro = socialNetworkContract.members[0]
console.log(socialNetworkContract.members[0])
await setUser(pro)
console.log(socialNetworkContract.members[0])
} catch (e) {
console.error(e)
}
}, [])
I have the route set in Routes.js as
<Route path="/member" exact component={Member} />
Use history.push('/') instead of window.location.href which will reload your whole page and you will lost your local state data.
const {withRouter} from "react-router-dom";
const Posts = (props) => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member');
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
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 );

Using the Context API gives me undefined

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.

Why is State Undefined? [Hooks/Redux]

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

Categories

Resources