Usereducer state is alway undefined when used with useContext - reactjs

I am trying to use useReduce along with useContext, when I console the value in a reducer, I am getting array of an object in the console, but when I try to access the state from another component, I am getting the state is undefined. State.map() is completely empty.
Here is my code
App.js
import React, { createContext, useReducer, useEffect } from 'react';
import Uploadproject from './Uploadproject';
import Getprojects, { projectget } from './Getprojects';
import reducer from './reducer/Usereducer';
export const Contextstate = createContext();
const App = () => {
const initialvalue = null;
const [state, dispatch] = useReducer(reducer, initialvalue);
const updatestate = async () => {
const data = await projectget();
dispatch({ type: 'add', payload: 'from app.js' });
};
useEffect(() => {
updatestate();
}, []);
return (
<>
<Contextstate.Provider value={{ state, dispatch }}>
<Uploadproject />
<Getprojects />
</Contextstate.Provider>
</>
);
};
export default App;
Usereducer.js
import { projectget } from '../Getprojects';
const reducer = (state, action) => {
if (action.type === 'add') {
projectget().then((result) => {
console.log(result);
// state = [ ...result]
state = result;
console.log(state);
return state;
});
}
};
export default reducer;
Getprojects.js
import React, { useContext, useEffect, useState } from 'react';
import { Contextstate } from './App';
const Getprojects = () => {
const { state, dispatch } = useContext(Contextstate);
const getstate = () => {
dispatch({ type: 'add', payload: 'from getprojects' });
};
useEffect(() => {
getstate();
}, []);
console.log(state);
return (
<>
<div>
<h1>Projects</h1>
{state &&
state.map((cur) => {
return (
<div key={cur._id}>
<h1>{cur.title}</h1>
<p>{cur.description}</p>
<button
onClick={() => {
deletproject(cur._id);
}}
>
Delete
</button>
</div>
);
})}
</div>
</>
);
};
export default Getprojects;
When I try to access the state from Getprojects component, its value is undefined. But inside a reducer if I, console am getting an array of object. In any other component, the state is undefined.
Any idea???

If you want to handle asyn logic in your application by using redux, you should pick one of Async Redux Middleware packages
redux-thunk (more easiest to config and good for small projects)
redux-saga
redux-observable etc.
Or you can just use useEffect and dispatch only result actions to it. For example:
useEffect(() => {
dispatch(getProjectActionStart());
projectget()
.then((result) => {
console.log(result);
// state = [ ...result]
state = result;
console.log(state);
dispatch(getProjectActionStart(state));
return state;
})
.catch(() => {
dispatch(getProjectActionFailed());
});
}, []);

Related

How to rerender when Redux state is changed

Hi I have 2 components.
The first component provides a read (useSelector) from the Redux state object and renders its contents
The second component ensures the insertion of new data into the same Redux state object
How to achieve that when a Redux state object changes with the help of the second component, the first component captures this change and renders the new content of the object again.
I tried to add in the component element:
useEffect(() => {
...some actions
}, [reduxStateObject]);
But it gives me too many requests.
/// EDIT add real example
component
import React from "react";
import { useSelector } from "react-redux";
const ToDoList = () => {
const { todos } = useSelector((state) => state.global);
return (
<div>
<h1>Active</h1>
{todos
?.filter((todo) => !todo.isCompleted)
.sort((a, b) => (a.deadline < b.deadline ? 1 : -1))
.map((todo, id) => {
const date = new Date(todo.deadline).toLocaleString();
return (
<div key={id}>
<p>{todo.text}</p>
<p>{date}</p>
</div>
);
})}
</div>
);
};
export default ToDoList;
component
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { getToDoItems } from "../redux/globalSlice";
import ToDoList from "../components/ToDoList";
const ToDoWall = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getToDoItems(1));
}, [dispatch]);
const submitForm = (e) => {
dispatch(postToDoItem(e.data));
};
return (
<>
<ToDoList />
<form onSubmit={submitForm}>
<input type="text"></input>
<input type="submit" value="" />
</form>
</>
);
};
export default ToDoWall;
/// EDIT add Reducer
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
todos: null,
};
export const globalSlice = createSlice({
name: "global",
initialState,
reducers: {
setItems: (state, action) => {
state.todos = action.payload;
},
},
});
export const { setItems } = globalSlice.actions;
export default globalSlice.reducer;
// Load todo items
export const getToDoItems = (id) => {
return (dispatch) => {
axios
.get(`https://xxx.mockapi.io/api/list/${id}/todos`)
.then((resp) => dispatch(setItems(resp.data)));
};
};
// Post a list name
export const postNameList = (data) => {
return (dispatch) => {
axios.post("https://xxx.mockapi.io/api/list", {
name: data,
});
};
};
// Post a todo item
export const postToDoItem = (id, data) => {
return (dispatch) => {
axios.post(
`https://xxx.mockapi.io/api/list/${id}/todos`,
{
listId: id,
title: data.title,
text: data.text,
deadline: +new Date(data.deadline),
isCompleted: false,
}
);
};
};
As far as I understood, you don't need to do anything. When you dispatch action to change state in redux store, it'll change, and all components that use that state will get it, you don't need to worry about updating anything.

useSelector is defined after dispatching but then undefined

I'm trying to get initial data from a reducer by dispatching action from App.js component, it works fine but when I switch to another component and load it with useSelector it gets undefined.
I have tried this line of code in Headphones.js but the second one returns undefined
const allData = useSelector((state) => state.allDataReducer);
const { loading, error, data } = allData;
App.js
const dispatch = useDispatch();
useEffect(() => {
dispatch(welcomeAction());
dispatch(listAllData);
}, [dispatch]);
allDataReducer.js
import {
LIST_ALL_DATA_FAIL,
LIST_ALL_DATA_REQUEST,
LIST_ALL_DATA_SUCCESS,
} from "../constants/shared";
export const allDataReducer = (state = { loading: true, data: {} }, action) => {
switch (action.type) {
case LIST_ALL_DATA_REQUEST:
return { loading: true };
case LIST_ALL_DATA_SUCCESS:
return { loading: false, data: action.payload };
case LIST_ALL_DATA_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
shared.js
import {
LIST_ALL_DATA_FAIL,
LIST_ALL_DATA_REQUEST,
LIST_ALL_DATA_SUCCESS,
} from "../constants/shared";
import Axios from "axios";
export const listAllData = async (dispatch) => {
dispatch({
type: LIST_ALL_DATA_REQUEST,
});
try {
const { data } = await Axios.get("/all");
dispatch({ type: LIST_ALL_DATA_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: LIST_ALL_DATA_FAIL, payload: error.message });
}
};
Headphones.js
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listheadphones } from "../actions/headphonesActions";
import BasicSection from "../components/BasicSection";
import Definer from "../components/Definer";
import LoadingBox from "../components/LoadingBox";
import MessageBox from "../components/MessageBox";
import ProductsCategories from "../components/ProductsCategories";
import BestAudioGear from "../components/BestAudioGear";
const Headphones = (props) => {
const dispatch = useDispatch();
const headphonesList = useSelector((state) => state.headphonesList);
const allData = useSelector((state) => state.allData);
const { loading, error, data } = allData; //undefined
//const { loading, error, headphones } = headphonesList;
console.log(headphonesList);
useEffect(() => {
dispatch(listheadphones());
}, [dispatch]);
return (
<div>
<Definer title="HEADPHONES" />
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
headphones.map((headphone) => (
<BasicSection
key={headphone.id}
name={headphone.headerName}
info={headphone.info}
mobile={headphone.mobile}
tablet={headphone.tablet}
desktop={headphone.desktop}
/>
))
)}
<ProductsCategories />
<BestAudioGear />
</div>
);
};
export default Headphones;
Github repo
Your description is still not specific enough, can't really pin down what the issue is. But here is some stuff I noticed:
dispatch(listAllData); somehow looks wrong to me, the action creator is usually a function that gets called: dispatch(listAllData());
Then where you define export const listAllData = async (dispatch) => { - this should be a function that returns a function if you're using the thunk middleware. You only defined a function.

Gettin 'null' while fetching data from api with React Context

I'm trying to get a bunch of articles from API using axios and useContext hook in React, but getting 'null' as a response.
This is the code from "State" file
import React, { useReducer } from "react";
import axios from "axios";
import ArticleContext from "./articleContext";
import articleReducer from "./articleReducer";
import { GET_ARTICLE } from "../types";
const ArticleState = (props) => {
const initialState = {
article: null,
};
const [state, dispatch] = useReducer(articleReducer, initialState);
const getArticle = async (id) => {
try {
const res = await axios.get(`/articles/${id}`);
dispatch({ type: GET_ARTICLE, payload: res.data });
} catch (err) {
console.log("errrrr");
}
};
return (
<ArticleContext.Provider
value={{
article: state.article,
getArticle,
}}
>
{props.children}
</ArticleContext.Provider>
);
};
export default ArticleState;
This is code from "Reducer"
import { GET_ARTICLE } from "../types";
// eslint-disable-next-line import/no-anonymous-default-export
export default (state, action) => {
switch (action.type) {
case GET_ARTICLE:
return {
...state,
article: action.payload,
};
default:
return state;
}
};
And finally code from the component, where i' trying to render data from the api call response and getting TypeError: article is null Am i missing something here? The main App component is also wrapped in <ArticleState></ArticleState>.
import React, { useEffect, useContext } from "react";
import ArticleContext from "../../context/article/articleContext";
const Article = () => {
const articleContext = useContext(ArticleContext);
const { article, getArticle } = articleContext;
useEffect(() => {
getArticle();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="article" key={article.id}>
<h2 className="article__title">{article.Title}</h2>
<p className="article__body">{article.preview}</p>
</div>
);
};
export default Article;
You should check if the article has been set before displaying its data.
Add a condition to the component before rendering the article informations:
const Article = () => {
const articleContext = useContext(ArticleContext);
const { article, getArticle } = articleContext;
useEffect(() => {
getArticle();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!article) {
return <>Loading article...</>
}
return (
<div className="article" key={article.id}>
<h2 className="article__title">{article.Title}</h2>
<p className="article__body">{article.preview}</p>
</div>
);
};

Clear / delete all states when login out react app

When a user log to a react app, I fill data to authState object. Inside the app I fill other state objects with data. I want to clear all those states when the user logout
for example I have this provider
import { createContext, useEffect, useReducer } from "react";
import auth from "./reducers/auth";
import pendiente from "./reducers/pendiente";
import historico from "./reducers/historico";
import authInitialState from "./initialStates/authInitialState";
import pendienteInitialState from "./initialStates/pendienteInitialState";
import historicoInitialState from "./initialStates/historicoInitialState";
export const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
const [authState, authDispatch] = useReducer(auth, [], () => {
const localData = localStorage.auth;
return localData ? JSON.parse(localData): authInitialState;
});
const [pendienteState, pendienteDispatch] = useReducer(
pendiente,
pendienteInitialState
);
const [historicoState, historicoDispatch] = useReducer(
historico,
historicoInitialState
);
useEffect(() => {
localStorage.auth = JSON.stringify(authState);
}, [authState]);
return (
<GlobalContext.Provider
value={{
authState,
authDispatch,
pendienteState,
pendienteDispatch,
historicoState,
historicoDispatch,
}}
>
{children}
</GlobalContext.Provider>
);
};
In Logout function I'm sending and action (logout) with 3 dispatchs.
const {
authState,
authDispatch,
pendienteDispatch,
historicoDispatch,
} = useContext(GlobalContext);
const handleLogout = () => {
logout(history)(authDispatch, pendienteDispatch, historicoDispatch);
};
Inside the action I send a dispatch an to every sate objcet to clear the data with it's initial state
This works fine, but I think this is not the correct way to do it
const logout = (history) => (
dispatch,
pendienteDispatch,
historicoDispatch
) => {
localStorage.removeItem("token");
dispatch({ type: LOGOUT_USER });
pendienteDispatch({ type: CLEAR_PENDIENTE_DATA });
historicoDispatch({ type: CLEAR_HISTORICO_DATA });
history.push("/");
};
¿Any ideas ?

When API post is successful, dispatch a get request - React/Redux

I have a small app that displays a component that is a list (JobsList) and another component that that contains a text field and submit button (CreateJob). While I am able to populate JobsList with API data (passing through Redux), I am not sure how I should update JobsList with a new API call, once I have successfully posted a new job in CreateJob. This is the code I have so far:
JobsList.js
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import JobCard from './JobCard';
import CreateJob from './CreateJob';
import api from './Api';
import { JOBS_LOADED } from './ActionTypes';
const JobsList = ({ jobs, onLoad }) => {
useEffect(() => {
const fetchJobs = async () => {
try {
const data = await api.Jobs.getAll();
onLoad({ data });
} catch (err) {
console.error(err);
}
};
fetchJobs();
}, [onLoad]);
return (
<Fragment>
<CreateJob />
{teams.map(job => (
<JobCard job={job} key={team.jobId} />
))}
</Fragment>
);
}
const mapStateToProps = state => ({
jobs: state.jobsReducer.teams
});
const mapDispatchToProps = dispatch => ({
onLoad: payload =>
dispatch({ type: JOBS_LOADED, payload }),
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsViewer);
CreateJob.js
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import api from './Api';
const CreateJob = () => {
const [state, setState] = React.useState({
jobName: '',
creator: ''
});
const handleInputChange = event => {
setState({
...state,
[event.target.name]: event.target.value
});
// validation stuff
}
const handleSubmit = async e => {
api.Jobs.create({state})
try {
await request;
// Reload the Jobs list so it does an another API request to get all new data
// DO I CALL A DISPATCH HERE?????
} catch (err) {
console.error(err);
}
}
return (
<div>
<TextField
name="jobName"
value={state.jobName || ''}
onChange={handleInputChange}
/>
<Button onClick={handleSubmit}>Create job</Button>
</div>
);
}
export default CreateJob;
JobsReducer.js
import { TEAMS_LOADED } from './ActionTypes';
export default (state = {teams: []}, action) => {
switch (action.type) {
case TEAMS_LOADED:
return {
...state,
teams: action.payload.data,
};
default:
return state;
}
};
In the success result in handleSubmit in CreateJob.js, how do I trigger/dispatch a new API call to update JobsList from CreateJob.js? I'm new to react/redux so apologies for any poor code. Any advice for a learner is greatly appreciated.
The simplified solution to take is wrapper the function for fetching jobs as a variable in the JobsList, and assign it to CreateJob as a prop. Then from the CreateJob, it's up to you to update the job list.
The shortage of this solution is it doesn't leverage redux as more as we can. It's better to create action creator for shared actions(fetch_jobs) in the JobsReducer.js and map these actions as props to the component which need it exactly.
JobsReducer.js
export const fetchJobsAsync = {
return dispatch => {
try {
const data = await api.Jobs.getAll();
dispatch({type: TEAMS_LOADED, payload: {data}})
} catch (err) {
console.error(err);
}
}
}
tips: You must install redux-thunk to enable the async action.
After, you will be able to fire the API to update the jobs(or teams anyway) from any component by dispatching the action instead of calling the API directly.
JobsList.jsx or CreateJob.js
const mapDispatchToProps = dispatch => ({
fetchAll: () => dispatch(fetchJobsAsync())
})
At the end of CreateJob.js, it's totally the same as calling the fetchAll to reload the jobs list like calling other regular functions.
And, if you are ok to go further, move the API call which creates new job to the reducer and wrapper it as an action. Inside it , dispatching the fetchJobsAsync if the expected conditions meet(If create new job finished successfully). Then you will end up with a more clearly component tree with only sync props without the data logic regarding to when/how to reload the jobs list.
Yes, your approach is absolutely right.
Once you have posted a new job, based on it's response you can trigger fetchJobs which you can pass as prop to <CreateJob fetchJobs={fetchJobs}/>.
For that you will have to declare it outside useEffect() like this:
import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import JobCard from './JobCard';
import CreateJob from './CreateJob';
import api from './Api';
import { JOBS_LOADED } from './ActionTypes';
const JobsList = ({ jobs, onLoad }) => {
const fetchJobs = async () => {
try {
const data = await api.Jobs.getAll();
onLoad({ data });
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchJobs();
}, [onLoad]);
return (
<Fragment>
<CreateJob fetchJobs={fetchJobs}/>
{teams.map(job => (
<JobCard job={job} key={team.jobId} />
))}
</Fragment>
);
}
const mapStateToProps = state => ({
jobs: state.jobsReducer.teams
});
const mapDispatchToProps = dispatch => ({
onLoad: payload =>
dispatch({ type: JOBS_LOADED, payload }),
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsViewer);
Once you trigger the api call new data will be loaded in redux state:
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import api from './Api';
const CreateJob = props => {
const [state, setState] = React.useState({
jobName: '',
creator: ''
});
const handleInputChange = event => {
setState({
...state,
[event.target.name]: event.target.value
});
// validation stuff
}
const handleSubmit = async e => {
api.Jobs.create({state})
try {
await request;
props.fetchJobs()
} catch (err) {
console.error(err);
}
}
return (
<div>
<TextField
name="jobName"
value={state.jobName || ''}
onChange={handleInputChange}
/>
<Button onClick={handleSubmit}>Create job</Button>
</div>
);
}
export default CreateJob;
As JobsList component is subscribed to the state and accepts state.jobsReducer.teams as props here:
const mapStateToProps = state => ({
jobs: state.jobsReducer.teams
});
The props will change on loading new jobs from <CreateJobs />and this change in props will cause <JobsLists /> to be re-rendered with new props.

Resources