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

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

Related

React Redux How to ensure Item is deleted without needing to refresh page using dispatch and slice

I am working on the following tutorial: https://www.youtube.com/watch?v=UXjMo25Nnvc&list=PLillGF-RfqbbQeVSccR9PGKHzPJSWqcsm&index=4
Full GitHub project here from Brad Traversy: https://github.com/bradtraversy/mern-tutorial
For the Delete Goals section (starting at 37:06 to 44:56 timestamp)
Dashboard file: https://github.com/htqanh305/vocab-app/blob/main/frontend/src/pages/Dashboard.jsx
import {useEffect} from 'react'
import {useNavigate} from 'react-router-dom' // for redirecting
import {useSelector, useDispatch} from 'react-redux' // grab user to check state
import GoalForm from '../components/GoalForm'
import GoalItem from '../components/GoalItem'
import Spinner from '../components/Spinner'
import {getGoals, reset} from '../features/goals/goalSlice'
function Dashboard() {
const navigate = useNavigate()
const dispatch = useDispatch()
const {user} = useSelector((state) => state.auth)
const {goals, isLoading, isError, message} = useSelector((state) => state.goals)
useEffect(() => {
if(isError) {
console.log(message)
}
if(!user) {
navigate('/login')
}
dispatch(getGoals())
console.log("I reached this point")
return () => {
dispatch(reset())
}
}, [user, navigate, isError, message, dispatch])
if(isLoading) {
return <Spinner />
}
if(!user) {
navigate('/login')
} else {
return (
<>
<section className="heading">
<h1>Welcome {user.name} </h1>
<p>Goals Dashboard</p>
</section>
<GoalForm/>
<section className="content">
{goals.length > 0 ? (
<div className="goals">
{goals.map((goal) => (
<GoalItem key={goal._id} goal={goal} />
))}
</div>
) :
(<h3> You have not set any goals </h3>)}
</section>
</>
)
}
}
export default Dashboard
goalSlice file: https://github.com/bradtraversy/mern-tutorial/blob/main/frontend/src/features/goals/goalSlice.js
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit'
import goalService from './goalService'
const initialState = {
goals: [],
isError: false,
isSuccess: false,
isLoading: false,
message: ''
}
// Create new goal
//thunkAPI object has a getState method that helps get anything we want/ any part of the state
// ie. get auth state to get token to access user so we can set/get goals
export const createGoal = createAsyncThunk('goals/create', async(goalData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.createGoal(goalData, token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
} )
// Get user goals
export const getGoals = createAsyncThunk('goals/getAll', async (_, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.getGoals(token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
// Delete goal
export const deleteGoal = createAsyncThunk('goals/delete', async(id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.deleteGoal(id, token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
} )
export const goalSlice = createSlice({
name: 'goal',
initialState,
reducers: {
reset: (state) => initialState
},
extraReducers: (builder) => {
builder
.addCase(createGoal.pending, (state) => {
state.isLoading = true
})
.addCase(createGoal.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals.push(action.payload)
})
.addCase(createGoal.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(getGoals.pending, (state) => {
state.isLoading = true
})
.addCase(getGoals.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals = action.payload
})
.addCase(getGoals.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(deleteGoal.pending, (state) => {
state.isLoading = true
})
.addCase(deleteGoal.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
// filter out the UI when delete a goal, only show goals that are not deleted
console.log("confirm")
state.goals = state.goals.filter(
goal => goal._id !== action.payload.id)
console.log(action.payload.id)
console.log(state.goals)
})
.addCase(deleteGoal.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
}
})
export const {reset} = goalSlice.actions
export default goalSlice.reducer
GoalItem file:
import {useDispatch} from 'react-redux'
import { deleteGoal } from '../features/goals/goalSlice'
function GoalItem({goal}) {
const dispatch = useDispatch()
return (
<div className="goal">
<div>
{new Date(goal.createdAt).toLocaleDateString('en-US')}
</div>
<h2>{goal.text}</h2>
<button onClick={() => dispatch(deleteGoal(goal._id))} className="close">X</button>
</div>
)
}
export default GoalItem
I'm following along in the tutorial, and no matter how much I try, I can't seem to get the delete goal functionality working.
When I create a new goal (item) and try to delete it, the item still remains on the page until I refresh or click on it twice. I'm not sure if it's something wrong with the goalSlice function or my Dashboard file, but for some reason it's not working.
I followed the tutorial to a tee, but it still looks like it isn't doing what it's supposed to.
Does anyone happen to know what is causing this Redux issue, or if I'm going crazy? Any suggestions or advice would be helpful. Thanks!

How to Pass Id correctly to Rest API Endpoint from React

I'm trying to fetch data through endpoint from Django Rest Framework
endpoint is :
/api/v1/categories/nested/{id}/
Problem is when I'm requesting with id, Django server show this error :
ValueError: Field 'id' expected a number but got 'undefined'.
[07/Feb/2022 15:53:01] "GET /api/v1/categories/nested/undefined/ HTTP/1.1" 500 162581
As this suggest I'm unable to Pass id correctly,
So need littl help to fix that
I'm using actions > reducer > store > component approach using react redux
action.js
export const listCategoryDetails = (id) => async (dispatch) => {
try {
dispatch({ type: CATEGORY_DETAIL_REQUEST });
const { data } = await axios.get(`/api/v1/categories/nested/${id}`); // Purpose to show nested brands[]
dispatch({
type: CATEGORY_DETAIL_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CATEGORY_DETAIL_FAIL,
payload:
error.response && error.response.data.detail
? error.response.data.detail
: error.message,
});
}
};
reducer.js
export const categoryDetailsReducer = (
state = { category: { } },
action
) => {
switch (action.type) {
case CATEGORY_DETAIL_REQUEST:
return { loading: true, ...state };
case CATEGORY_DETAIL_SUCCESS:
return { loading: false, category: action.payload };
case CATEGORY_DETAIL_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
store.js
const reducer = combineReducers({
categoryDetail: categoryDetailsReducer,
});
component
function CategoryDetail({ match, history }) {
// const { id } = useParams();
// console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails());
}, [dispatch, match]);
return <div>
{categories.map(category => (
<Col key={category.id} sm={12} md={8} lg={4} xl={3} >
<h1><strong>{category.title}</strong></h1>))}
</div>;
}
export default CategoryDetail;
const id = ...
and pass it to dispatch function dispatch(listCategoryDetails(id))
Before
// const { id } = useParams();
// console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails());
}, [dispatch, match]);
After
const { id } = useParams();
console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails(id));
}, [dispatch, match]);
Inside UseEffect You Are Not Passing id Variable So its Saying Id Is Undefined

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

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

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