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>
);
};
Related
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.
I just can't decide the pattern I want to follow.
I'm implementing what I call a UserParent component. Basically a list of users and when you click on a user, it loads their resources.
Approach 1: Redux
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { NavList } from '../nav/NavList'
import { ResourceList } from '../resource/ResourceList'
import { getUserResources, clearResources } from './userSlice'
import CircularProgress from '#mui/material/CircularProgress';
import { getAllUsers } from './userSlice';
export const UserParent = () => {
const users = useSelector((state) => state.users.users )
const resources = useSelector((state) => state.users.user.resources )
const [highLightedUsers, setHighLightedItems] = useState([]);
const isLoading = useSelector((state) => state.users.isLoading)
let dispatch = useDispatch();
useEffect(() => {
dispatch(getAllUsers());
}, [])
const onUserClick = (user) => {
if (highLightedUsers.includes(user.label)) {
setHighLightedItems([])
dispatch(clearResources())
} else {
setHighLightedItems([...highLightedUsers, user.label])
dispatch(getUserResources(user.id))
}
}
return(
<>
{ isLoading === undefined || isLoading ? <CircularProgress className="search-loader" /> :
<div className="search-container">
<div className="search-nav">
<NavList
items={users}
onItemClick={onUserClick}
highLightedItems={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div> }
</>
)
}
And then we have the reducer code:
import { createSlice } from '#reduxjs/toolkit';
import Api from '../../services/api';
const INITIAL_STATE = {
users: [],
isLoading: true,
user: { resources: [] }
};
export const userSlice = createSlice({
name: 'users',
initialState: INITIAL_STATE,
reducers: {
loadAllUsers: (state, action) => ({
...state,
users: action.payload,
isLoading: false
}),
toggleUserLoader: (state, action) => ({
...state,
isLoading: action.payload
}),
loadUserResources: (state, action) => ({
...state, user: { resources: action.payload }
}),
clearResources: (state) => ({
...state,
isLoading: false,
user: { resources: [] }
})
}
});
export const {
loadAllUsers,
toggleUserLoader,
loadUserResources,
clearResources
} = userSlice.actions;
export const getAllUsers = () => async (dispatch) => {
try {
const res = await Api.fetchAllUsers()
if (!res.errors) {
dispatch(loadAllUsers(res.map(user => ({id: user.id, label: user.full_name}))));
} else {
throw res.errors
}
} catch (err) {
alert(JSON.stringify(err))
}
}
export const getUserResources = (userId) => async (dispatch) => {
try {
const res = await Api.fetchUserResources(userId)
if (!res.errors) {
dispatch(loadUserResources(res));
} else {
throw res.errors
}
} catch (err) {
alert(JSON.stringify(err))
}
}
export default userSlice.reducer;
This is fine but I am following this pattern on every page in my app. While it is easy follow I don't believe I'm using global state properly. Every page makes and API call and loads the response into redux, not necessarily because it needs to be shared (although it may be at some point) but because it's the pattern I'm following.
Approach 2: Local State
import React, { useEffect, useState } from 'react'
import { NavList } from '../nav/NavList'
import { ResourceList } from '../resource/ResourceList'
import CircularProgress from '#mui/material/CircularProgress';
import Api from '../../services/api';
export const UserParent = () => {
const [users, setUsers] = useState([])
const [resources, setResources] = useState([])
const [highLightedUsers, setHighLightedItems] = useState([]);
const [isLoading, setIsLoading] = useState(true)
const getUsers = async () => {
try {
const res = await Api.fetchAllUsers()
setUsers(res.map(user => ({id: user.id, label: user.full_name})))
setIsLoading(false)
} catch (error) {
console.log(error)
}
}
const getUserResources = async (userId) => {
try {
setIsLoading(true)
const res = await Api.fetchUserResources(userId)
setResources(res)
setIsLoading(false)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getUsers()
}, [])
const onUserClick = (user) => {
if (highLightedUsers.includes(user.label)) {
setHighLightedItems([])
} else {
setHighLightedItems([...highLightedUsers, user.label])
getUserResources(user.id)
}
}
return(
<>
{ isLoading === undefined || isLoading ? <CircularProgress className="search-loader" /> :
<div className="search-container">
<div className="search-nav">
<NavList
items={users}
onItemClick={onUserClick}
highLightedItems={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div>}
</>
)
}
What I like about this is that it uses local state and doesn't bloat global state however, I don't like that it still has business logic in the component, I could just move these to a different file but first I wanted to try React Query instead.
Approach 3: React Query
import React, { useState } from 'react'
import { NavList } from '../nav/NavList'
import { ResourceList } from '../resource/ResourceList'
import CircularProgress from '#mui/material/CircularProgress';
import Api from '../../services/api';
import { useQuery } from "react-query";
export const UserParent = () => {
const [resources, setResources] = useState([])
const [highLightedUsers, setHighLightedItems] = useState([]);
const getUsers = async () => {
try {
const res = await Api.fetchAllUsers()
return res
} catch (error) {
console.log(error)
}
}
const { data, status } = useQuery("users", getUsers);
const getUserResources = async (userId) => {
try {
const res = await Api.fetchUserResources(userId)
setResources(res)
} catch (error) {
console.log(error)
}
}
const onUserClick = (user) => {
if (highLightedUsers.includes(user.label)) {
setHighLightedItems([])
} else {
setHighLightedItems([...highLightedUsers, user.label])
getUserResources(user.id)
}
}
return(
<>
{ status === 'loading' && <CircularProgress className="search-loader" /> }
<div className="search-container">
<div className="search-nav">
<NavList
items={data.map(user => ({id: user.id, label: user.full_name}))}
onItemClick={onUserClick}
highLightedItems={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div>
</>
)
}
This is great but there is still business logic in my component, so I can move those functions to a separate file and import them and then I end up with this:
import React, { useState } from 'react'
import { UserList } from '../users/UserList'
import { ResourceList } from '../resource/ResourceList'
import CircularProgress from '#mui/material/CircularProgress';
import { getUsers, getUserResources } from './users'
import { useQuery } from "react-query";
export const UserParent = () => {
const [resources, setResources] = useState([])
const [highLightedUsers, setHighLightedItems] = useState([]);
const { data, status } = useQuery("users", getUsers);
const onUserClick = async (user) => {
if (highLightedUsers.includes(user.full_name)) {
setHighLightedItems([])
} else {
setHighLightedItems([...highLightedUsers, user.full_name])
const res = await getUserResources(user.id)
setResources(res)
}
}
return(
<>
{ status === 'loading' && <CircularProgress className="search-loader" /> }
<div className="search-container">
<div className="search-nav">
<UserList
users={data}
onUserClick={onUserClick}
highLightedUsers={highLightedUsers}
/>
</div>
<div className="search-results">
<ResourceList resources={resources} />
</div>
</div>
</>
)
}
In my opinion this is so clean! However, is there anything wrong with the first approach using Redux? Which approach do you prefer?
The first approach you are using shows a very outdated style of Redux.
Modern Redux is written using the official Redux Toolkit (which is the recommendation for all your production code since 2019. It does not use switch..case reducers, ACTION_TYPES, immutable reducer logic, createStore or connect. Generally, it is about 1/4 of the code.
What RTK also does is ship with RTK-Query, which is similar to React Query, but even a bit more declarative than React Query. Which one of those two you like better is probably left to personal taste.
I'd suggest that if you have any use for Redux beyond "just api fetching" (which is a solved problem given either RTK Query or React Query), you can go with Redux/RTK-Query.
If you don't have any global state left after handling api caching, you should probably just go with React Query.
As for learning modern Redux including RTK Query, please follow the official Redux tutorial.
Personally I prefer React-Query for all API-calls, it is great it useMutate and how it manages re-fetching, invalidating queries and more.
I am using your third approach where I create the queries in separate files and then import them where needed.
So far it has been great, and I am using RecoilJS for managing global states. And with the right approach there is really not much that actually needs to be in a global state IMO. Some basic auth/user info and perhaps notification management. But other than that I have been putting less and less in global states keeping it much simpler and scoped.
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());
});
}, []);
I'm trying to learn mern stack and redux by making a small e-commerce website and i have this state.cartItmes is not iterable error in redux action and i have no idea what is causing it or how to fix it
So because i hope one day i will became a Frontend developer i'm doing what a developer does...i'm asking you guys what i did wrong in my spagetti code
Cart Reducer
export const cartReducer = (state = { cartItems: [] }, action) => {
if (action.type === "ADD_TO_CART") {
return { cartItems: [...state.cartItems, action.payload] };
}
return state;
};
Cart Action
import axios from "axios";
export const addToCart = (id) => async (dispatch, getState) => {
try {
const { data } = await axios.get(`/products/${id}`);
dispatch({ type: "ADD_TO_CART", payload: data });
localStorage.setItem(
"cartItems",
JSON.stringify(getState().cart.cartItems)
);
} catch (error) {
console.log(error.message);
}
};
Cart component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addToCart } from "../actions/CartActions";
import { useParams } from "react-router-dom";
const Cart = () => {
const dispatch = useDispatch();
const { id } = useParams();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
console.log(cartItems);
useEffect(() => {
dispatch(addToCart(id));
}, [dispatch, id]);
return (
<section>
<div className="cart">
<div className="cart-items">Cart</div>
<div className="buy-items"></div>
</div>
</section>
);
};
export default Cart;
After analyzing your code, I think the issue may be the empty array that you initially set. You should try using some default value as initial value, I think this should solve the issue.
Also, it is generally not a good practice to put async logic inside a reducer. You may want to implement some middleware for this. Here's some more info on it: https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-middleware-to-enable-async-logic
I am trying to create a context for saveing data from an Api response. But it gives me an error.
I don't know how to solve the error.
This screen is a show screen. In the index screen I can use another context and it works well.
I want to create another context for saving data from other Api call. I am trying to copy it but it gives me an error.
Thanks.
TypeError: undefined is not an object (evaluating '_useContext.state')
CONTEXT
import createDataContext from './createDataContext';
import jsonServer from '../api/api';
const currencyPrices = (state, action) => {
switch (action.type) {
case 'show_prices':
return action.payload;
default:
return state;
}
};
const showPrices = (dispatch) => {
return async (id) => {
const response = await jsonServer.get(`/prices/${id}`);
dispatch({type: 'show_prices', payload: response.data[0]});
};
};
export const {Context, Provider} = createDataContext(
currencyPrices,
{showPrices},
[],
);
CREATE DATA CONTEXT
import React, {useReducer} from 'react';
export default (reducer, actions, initialState) => {
const Context = React.createContext();
const Provider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
// actions === { addBlogPost: (dispatch) => { return () => {} } }
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{state, ...boundActions}}>
{children}
</Context.Provider>
);
};
return {Context, Provider};
};
SCREEN
/* eslint-disable react-hooks/rules-of-hooks */
import React, {useContext, useEffect} from 'react';
import {View, Text} from 'react-native';
import {Context} from '../context/currencyPrices';
const showScreen = ({navigation}) => {
const {state, showPrices} = useContext(Context);
useEffect(() => {
showPrices(1);
const listener = navigation.addListener('didFocus', () => {
showPrices(1);
});
return () => {
listener.remove();
};
});
return (
<View>
<Text>{state.length}</Text>
</View>
);
};
export default showScreen;
in app.js
import {Provider as ProviderName}
<ProciderName>
<App/>
</ProviderName}
you should wrap provider in mani root like app.js