Why is State Undefined? [Hooks/Redux] - javascript

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

Related

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

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

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

Redux + Hooks useDispatch() in useEffect calling action twice

I'm beginner in redux & hooks. I am working on form handling and trying to call an action through useDispatch hooks but it is calling my action twice.
I'm referring this article.
Here is the example:
useProfileForm.js
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchProfile } from '../../../redux/profile/profile.actions';
const useProfileForm = (callback) => {
const profileData = useSelector(state =>
state.profile.items
);
let data;
if (profileData.profile) {
data = profileData.profile;
}
const [values, setValues] = useState(data);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchProfile());
}, [dispatch]);
const handleSubmit = (event) => {
if (event) {
event.preventDefault();
}
callback();
};
const handleChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
return {
handleChange,
handleSubmit,
values,
}
};
export default useProfileForm;
Action
export const FETCH_PROFILE_BEGIN = "FETCH_PROFILE_BEGIN";
export const FETCH_PROFILE_SUCCESS = "FETCH_PROFILE_SUCCESS";
export const FETCH_PROFILE_FAILURE = "FETCH_PROFILE_FAILURE";
export const ADD_PROFILE_DETAILS = "ADD_PROFILE_DETAILS";
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
function getProfile() {
return fetch("url")
.then(handleErrors)
.then(res => res.json());
}
export function fetchProfile() {
return dispatch => {
dispatch(fetchProfileBegin());
return getProfile().then(json => {
dispatch(fetchProfileSuccess(json));
return json;
}).catch(error =>
dispatch(fetchProfileFailure(error))
);
};
}
export const fetchProfileBegin = () => ({
type: FETCH_PROFILE_BEGIN
});
export const fetchProfileSuccess = profile => {
return {
type: FETCH_PROFILE_SUCCESS,
payload: { profile }
}
};
export const fetchProfileFailure = error => ({
type: FETCH_PROFILE_FAILURE,
payload: { error }
});
export const addProfileDetails = details => {
return {
type: ADD_PROFILE_DETAILS,
payload: details
}
};
Reducer:
import { ADD_PROFILE_DETAILS, FETCH_PROFILE_BEGIN, FETCH_PROFILE_FAILURE, FETCH_PROFILE_SUCCESS } from './profile.actions';
const INITIAL_STATE = {
items: [],
loading: false,
error: null
};
const profileReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ADD_PROFILE_DETAILS:
return {
...state,
addProfileDetails: action.payload
}
case FETCH_PROFILE_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_PROFILE_SUCCESS:
return {
...state,
loading: false,
items: action.payload.profile
};
case FETCH_PROFILE_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
export default profileReducer;
**Component:**
import React from 'react';
import { connect } from 'react-redux';
import useProfileForm from './useProfileForm';
import { addProfileDetails } from '../../../redux/profile/profile.actions';
const EducationalDetails = () => {
const { values, handleChange, handleSubmit } = useProfileForm(submitForm);
console.log("values", values);
function submitForm() {
addProfileDetails(values);
}
if (values) {
if (values.error) {
return <div>Error! {values.error.message}</div>;
}
if (values.loading) {
return <div>Loading...</div>;
}
}
return (
<Card>
...some big html
</Card>
)
}
const mapDispatchToProps = dispatch => ({
addProfileDetails: details => dispatch(details)
});
export default connect(null, mapDispatchToProps)(EducationalDetails);
Also when I'm passing data from const [values, setValues] = useState(data); useState to values then ideally I should receive that in component but I'm not getting as it is showing undefined.
const { values, handleChange, handleSubmit } = useProfileForm(submitForm);
values is undefined
The twice dispatch of action is probably because you have used React.StrictMode in your react hierarchy.
According to the react docs, in order to detect unexpected sideEffects, react invokes a certain functions twice such as
Functions passed to useState, useMemo, or useReducer
Now since react-redux is implemented on top of react APIs, actions are infact invoked twice
Also when I'm passing data from const [values, setValues] = useState(data); useState to values then ideally I should receive that in component but I'm not getting as it is showing undefined.
To answer this question, you must know that values is not the result coming from the response of dispatch action from reducer but a state that is updated when handleChange is called so that is supposed to remain unaffected by the action
I think you mean to expose the redux data from useProfileForm which forgot to do
const useProfileForm = (callback) => {
const profileData = useSelector(state =>
state.profile.items
);
let data;
if (profileData.profile) {
data = profileData.profile;
}
const [values, setValues] = useState(data);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchProfile());
}, [dispatch]);
const handleSubmit = (event) => {
if (event) {
event.preventDefault();
}
callback();
};
const handleChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
return {
handleChange,
handleSubmit,
values,
data // This is the data coming from redux store on FetchProfile and needs to logged
}
};
export default useProfileForm;
You can use the data in your component like
const { values, handleChange, handleSubmit, data } = useProfileForm(submitForm);

React renders my Component before it actually has Data

So I'm hard stuck on this Problem... normally I would just do a "ComponentDidMount" but since I'm trying to avoid using classes and only use react hooks I got stuck with the Problem.
My Component renders before it gets any Data from the API, so my .map function won't work as it has not recieve any data.
Shop.js
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { listShops } from "../../Redux/actions/shopActions";
const Shop = () => {
const userShop = useSelector(state => state.shop);
const auth = useSelector(state => state.auth);
const dispatch = useDispatch();
useEffect(() => {
dispatch(listShops(auth));
}, []);
console.log("Look at my userShop",userShop.shop)
return (
<div>
{userShop.map(shop=>(<div>{shop}</div>))}
{console.log("How often do I Render?")}
</div>
);
};
export default Shop;
ShopAction.js
import {GET_SHOPS} from "./types";
export const listShops = userData => async dispatch =>{
const userId = userData.user.id;
await axios.get(`/api/shops/shops/user/${userId}`)
.then(
res => {
const user = res.data;
dispatch({
type: GET_SHOPS,
payload: user.shops
})})
}
shopReducer.js
const initialState = {}
export default function(state = initialState, action) {
switch (action.type) {
case GET_SHOPS:
return {
...state,
shop:action.payload
}
default:
return state;
}
}
if(!userShop){
return <h1>loading<h1>;
}
return (
<div>
{userShop.map(shop=>(<div>{shop}</div>))}
</div>
);
Return an empty array if state.shop is undefined using short-circuit evaluation:
const userShop = useSelector(state => state.shop || []);
return (
<div>
{userShop && userShop.map(shop=>(<div>{shop}</div>))}
</div>
);

Categories

Resources