I have an app built with React, Redux that pulls data from a RESTful service sitting in my local. I tested the implementation with dummy data and works fine. However, when I hook up the async service the calls result in havoc with the below error:
Here is the code
reducer.js
import {
LOAD_ALL_PRODUCTS_SUCCESS,
LOAD_ALL_PRODUCTS_REQUEST,
LOAD_ALL_PRODUCTS_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
LOAD_PRODUCT_FAIL,
} from './actions';
export const productData = (state = { loader: {}, products: [] }, action) => {
const { type, payload } = action;
switch (type) {
case LOAD_ALL_PRODUCTS_REQUEST: {
return { loader: true, products: [] };
}
case LOAD_ALL_PRODUCTS_SUCCESS: {
return { loader: false, products: payload };
}
case LOAD_ALL_PRODUCTS_FAIL: {
return { loader: false, error: payload };
}
default:
return state;
}
};
thunk.js
import axios from 'axios';
import { mockData } from '../MockData';
import {
loadAllProductFailed,
loadAllProductRequest,
loadAllProductSuccess,
LOAD_PRODUCT_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
} from './actions';
export const loadInitialProducts = () => async (dispatch) => {
try {
dispatch(loadAllProductRequest());
//this is where the issues is
const response = await axios.get('http://localhost:8080/v1/product/all');
const payload = await response.data;
console.log(payload);
dispatch(loadAllProductSuccess(payload));
} catch (error) {
dispatch(
loadAllProductFailed(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
);
}
};
export const loadProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: LOAD_PRODUCT_REQUEST });
//do a axios api call for product api
dispatch({
type: LOAD_PRODUCT_SUCCESS,
payload: mockData.find(({ productId }) => productId == id),
});
} catch (error) {
dispatch({
type: LOAD_PRODUCT_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const LOAD_ALL_PRODUCTS_REQUEST = 'LOAD_PRODUCTS_REQUEST';
export const loadAllProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_REQUEST,
});
export const LOAD_ALL_PRODUCTS_SUCCESS = 'LOAD_ALL_PRODUCTS_SUCCESS';
export const loadAllProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_SUCCESS,
payload: payload,
});
export const LOAD_ALL_PRODUCTS_FAIL = 'LOAD_ALL_PRODUCTS_FAIL';
export const loadAllProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
export const LOAD_PRODUCT_REQUEST = 'LOAD_PRODUCT_REQUEST';
export const loadProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_FAIL,
});
export const LOAD_PRODUCT_SUCCESS = 'LOAD_PRODUCT_SUCCESS';
export const loadProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: payload,
});
export const LOAD_PRODUCT_FAIL = 'LOAD_PRODUCT_FAIL';
export const loadProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
Home.js
import React, { useState, useEffect } from 'react';
import { conwayAPI } from '../ConwayAPI';
import { Container, Col, Row } from 'react-bootstrap';
import Product from './Product';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'lodash';
import { loadInitialProducts } from '../app/thunk';
const Home = () => {
//local state maintained only for this component
const [filterProducts, setFilterProducts] = useState([]);
const dispatch = useDispatch();
const productList = useSelector((state) => state.productData);
const { loader, error, products } = productList;
useEffect(() => {
dispatch(loadInitialProducts());
}, [dispatch, products]);
const doSearch = (text) => {
_.isEmpty(text)
? setFilterProducts(products)
: setFilterProducts(
filterProducts.filter((product) =>
product.productName.toLowerCase().includes(text.toLowerCase())
)
);
};
return (
<Container fluid>
<Row md={7} lg={5} className='justify-content-md-center'>
{filterProducts.length &&
filterProducts.map((datam, key) => {
return (
<Col key={key}>
<Product product={datam} key={key} />
</Col>
);
})}
</Row>
</Container>
);
};
export default Home;
When I click on a Nav panel the Home.js gets called and the error starts. What can I do differently to eliminate this error?
Related
I am using axios to fetch data and then want to render the component. For that, I have loading which gets set to true when fetching and to false when all the data has come.
But I am getting error. Is there a way to trigger useEffect before rendering of component ?
Following is the code:
GithubReducer.js
import {
SET_USERS,
CLEAR_USERS,
SET_LOADING,
SET_USER,
CLEAR_USER,
} from "../types";
const GithubReducer = (state, action) => {
switch (action.type) {
case SET_USERS: {
return { ...state, users: action.payload };
}
case CLEAR_USERS: {
return { ...state, users: [] };
}
case SET_LOADING: {
return { ...state, loading: action.payload };
}
case SET_USER: {
return { ...state, user: action.payload };
}
case CLEAR_USER: {
return { ...state, user: null };
}
default:
return state;
}
};
export default GithubReducer;
GithubState.js
import React, { useReducer } from "react";
import axios from "axios";
import {
SET_USERS,
CLEAR_USERS,
SET_LOADING,
SET_USER,
CLEAR_USER,
} from "../types";
import GithubReducer from "./GithubReducer";
import GithubContext from "./GithubContext";
const GithubState = (props) => {
const initialState = {
loading: false,
users: [],
user: null,
};
const [state, dispatch] = useReducer(GithubReducer, initialState);
const setLoading = (val) => dispatch({ type: SET_LOADING, payload: val });
const getGithubUsers = async () => {
setLoading(true);
dispatch({ type: CLEAR_USER });
const res = await axios.get(`https://api.github.com/users`);
dispatch({
type: SET_USERS,
payload: res.data,
});
setLoading(false);
};
const clearUsers = () => {
dispatch({ type: CLEAR_USERS });
};
const searchUsersWithName = async (username) => {
setLoading(true);
const res = await axios.get(
`https://api.github.com/search/users?q=${username}`
);
dispatch({ type: SET_USERS, payload: res.data.items });
setLoading(false);
};
const fetchGithubUserProfile = async (username) => {
setLoading(true);
const res = await axios.get(`https://api.github.com/users/${username}`);
dispatch({ type: SET_USER, payload: res.data });
setLoading(false);
};
return (
<GithubContext.Provider
value={{
getGithubUsers,
clearUsers,
searchUsersWithName,
fetchGithubUserProfile,
users: state.users,
loading: state.loading,
user: state.user,
}}
>
{props.children}
</GithubContext.Provider>
);
};
export default GithubState;
User.js
import React, { useContext, useEffect } from "react";
import { useParams } from "react-router-dom";
import GithubContext from "../../context/github/GithubContext";
import Spinner from "../layout/Spinner";
const User = () => {
const { fetchGithubUserProfile, user, loading } = useContext(GithubContext);
const { username } = useParams();
useEffect(() => {
fetchGithubUserProfile(username);
// eslint-disable-next-line
}, []);
if (loading) return <Spinner />;
else {
return (
<div className="user">
<button>Go Back</button>
<section className="about">{user.login}</section>
</div>
);
}
};
export default User;
And, this is the error I am getting:
TypeError: Cannot read properties of null (reading 'login')
User
D:/anubh/Desktop/github-finder/src/components/users/User.js:21
18 | return (
19 | <div className="user">
20 | <button>Go Back</button>
> 21 | <section className="about">{user.login}</section>
| ^ 22 | </div>
23 | );
24 | }
Very simple. You can't. useEffect runs after componentDidMount, or after the JSX has been rendered.
Here is a solution. Render your JSX conditionally depending on state, which you can set once your data is retrieved.
return (
{data ? <MyComponent /> : null}
)
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.
So I have a mobile app in react native for which I am trying to create the auth flow properly.I had this first as a web app and then now trying to do the same as a mobile app. I am using redux for state management.
Here is the flow: Once the user logs in or registers, I send the data to the backend using axios. The backend generates a user token. I would like to create a system where I can fetch the token when a user logs in and store that with AsyncStorage so that I can directly log the user in when he or she tries to open the app again.
Here is the code, more than happy to answer any questions, this was done through a react tutorial really:
LoginScreen.js
const Login = ( { navigation }) => {
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [message, setMessage] = useState(null)
// Errors
const [EmailMessage, setEmailMessage] = useState(null);
const [PasswordMessage, setPasswordMessage] = useState(null);
const dispatch = useDispatch();
const userLogin = useSelector((state) => state.userLogin);
const { loading, error, userInfo } = userLogin
useEffect(() => {
if (userInfo) {
navigation.navigate('MainTabs', { screen: 'Home'});
}
}, [navigation, userInfo]);
const submitHandler = (e) => {
e.preventDefault();
if (!email) {
alert('Please fill in the email');
return
};
if (!password) {
alert('Please fill in the password');
return
}
dispatch(login(email, password));
};
The return part is the frontend code so left that out for the sake of brevity
Now the redux part:
userActions.js
import axios from "axios";
import { USER_LOGIN_FAIL, USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS, USER_LOGOUT, USER_REGISTER_FAIL, USER_REGISTER_SUCCESS, USER_REGISTER_REQUEST } from "../constants/userConstants"
import AsyncStorage from '#react-native-async-storage/async-storage';
export const login = (email, password) => async(dispatch) => {
try {
dispatch({ type: USER_LOGIN_REQUEST });
const config = {
headers: {
"Content-type": "application/json"
}
}
const {data} = await axios.post("api/url", {email, password}, config)
dispatch({type: USER_LOGIN_SUCCESS, payload:data});
await AsyncStorage.setItem("userInfo", JSON.stringify(data))
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
console.log("This login attempt is unsuccessful");
}
}
export const logout = () => async (dispatch) => {
await AsyncStorage.removeItem("userInfo")
dispatch({ type: USER_LOGOUT });
};
export const register = (full_name, email, password, social) => async(dispatch) => {
try {
dispatch({type: USER_REGISTER_REQUEST});
const config = {
headers: {
"Content-type": "application/json"
}
};
const {data} = await axios.post("api/url", {full_name, email, password, social}, config);
dispatch({ type: USER_REGISTER_SUCCESS, payload: data});
dispatch({ type: USER_LOGIN_SUCCESS, payload: data});
await AsyncStorage.setItem("userInfo", JSON.stringify(data))
} catch (error) {
dispatch({type: USER_REGISTER_FAIL, payload:
error.response && error.response.data.message
? error.response.data.message
: error.message
})
}}
userReducers.js
import { USER_LOGIN_REQUEST, USER_REGISTER_FAIL, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS } from "../constants/userConstants";
import { USER_LOGIN_SUCCESS, USER_LOGIN_FAIL, USER_LOGOUT } from "../constants/userConstants";
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return {loading: true}
case USER_LOGIN_SUCCESS:
return {loading: false, userInfo: action.payload}
case USER_LOGIN_FAIL:
return {loading: false, error:action.payload}
case USER_LOGOUT:
return {}
default:
return state;
}
}
export const userRegisterReducer = (state = {}, action) => {
switch (action.type) {
case USER_REGISTER_REQUEST:
return {loading:true}
case USER_REGISTER_SUCCESS:
return {loading:false, userInfo: action.payload}
case USER_REGISTER_FAIL:
return {loading:false, error: action.payload}
default:
return state;
}
}
store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
import { userLoginReducer, userRegisterReducer } from './src/redux/reducers/userReducers';
import AsyncStorage from '#react-native-async-storage/async-storage';
const reducer = combineReducers({
//contains reducers
userLogin: userLoginReducer,
userRegister: userRegisterReducer
});
const middleware = [thunk];
const getData = async () => {
try {
const value = await AsyncStorage.getItem("userInfo")
return value != null ? JSON.parse(value) : null;
} catch(e) {
console.log("this attempt is not successful");
}
}
const userInfoFromStorage = getData() || null;
alert(JSON.stringify(userInfoFromStorage))
const initialState = {
userLogin: {userInfo: userInfoFromStorage}
};
const store = createStore(
reducer,
initialState,
applyMiddleware(...middleware)
);
export default store
I would appreciate any help here as I am not able to resolve how to solve this is this is a promise issue on fetch or something more general than that. I have a token generated in the backend once I register a user. I would really appreciate if anyone knows of the best way on how to fetch and save that token and log the user in if the user had already logged in
First, create a file with the name authProvider.ts. This will hold objects, with key as isAuthenticated and async getAuthToken() with callback function as property.
const authProvider: any = {
isAuthenticated: false,
authToken: null,
async getAuthToken(callback: VoidFunction) {
await GetSessionData('Access-token').then(
res => {
if (res) {
authProvider.isAuthenticated = true;
authProvider.authToken = res;
callback();
} else {
authProvider.isAuthenticated = false;
authProvider.authToken = res;
callback();
}
}
);
}
};
export { authProvider };
export const GetSessionData = async (key: string) => {
return await AsyncStorage.getItem(
key,
(err, value) => {
if (err) {
console.log(err);
} else {
return value;
}
}
);
}
In app.tsx
In App component we will call the above authProvider to get the token from Async storage using await. Once done it will execute the below callback function logic.
const App = () => {
const [isLoading, setLoading] = useState<boolean>(true);
useEffect(() => {
authProvider.getAuthToken(() => {
if (authProvider.isAuthenticated) {
store.dispatch(isAuthenticatedUser(true));
setLoading(false);
} else {
store.dispatch(isAuthenticatedUser(false));
setLoading(false);
// *** redirect to login page logic ***.
}
});
}, []);
return (
<>
<Provider store={store}>
{
!isLoading ?
<Dashboard /> :
<AppLoader />
}
</Provider>
</>
);
}
I am passing value into the redux store through reducer. And I am displaying that value in the component.
But it says cannot read property name of undefined
Even whats weird is when I map the product, I can see product value in the console and when I don't map, I don't see the product value in the console. Please help me with this
Please find the code here
Component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listProductDetails } from "./actions/productActions";
const Playground = () => {
const dispatch = useDispatch();
const productDetails = useSelector((state) => state.productDetails);
const { product } = productDetails;
useEffect(() => {
dispatch(listProductDetails("pod2"));
}, [dispatch]);
return (
<div>
<h1>
<h1>{product[0].name}</h1>
</h1>
</div>
);
};
export default Playground;
Reducer
export const productDetailsReducers = (state = { product: [] }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return { loading: true, ...state };
case PRODUCT_DETAILS_SUCCESS:
return {
loading: false,
product: action.payload,
};
case PRODUCT_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
Action
export const listProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/products/${id}`);
console.log("this is the data");
console.log(data);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Store
const reducer = combineReducers({
productDetails: productDetailsReducers,
});
It's always better to have a condition before accessing the data
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listProductDetails } from "./actions/productActions";
const Playground = () => {
const dispatch = useDispatch();
const productDetails = useSelector((state) => state.productDetails);
const { product } = productDetails;
useEffect(() => {
dispatch(listProductDetails("pod2"));
}, [dispatch]);
return (
<div>
{
product &&
product.length ?
<h1>
<h1>{product[0].name || ""}</h1>
</h1>
: null
}
</div>
);
};
export default Playground;
I need your help. I'm creating an app with useContext and useReducer hooks and I a have problems. I have a function to get all notes from my database. I called that function inside off useEffect hook:
import React, { useContext, useEffect } from "react";
import { useTranslation } from "react-i18next";
//Context
import AuthContext from "../../context/auth/authContext";
import NoteContext from '../../context/notes/noteContext';
//Components
import { Row, Col, Container, Button } from "react-bootstrap";
import Canva from '../Canva/Canva';
import Note from '../Note/Note';
const Dashboard = () => {
const { t, i18n } = useTranslation();
const authContext = useContext(AuthContext);
const { authUser, user } = authContext;
const noteContext = useContext(NoteContext);
const { notes, getNotes, addNote } = noteContext;
useEffect(() => {
getNotes();
}, []);
return (
<>
<Container>
<Row>
<Col sm={12} md={10}>
<Button onClick={() => addNote()} type='button' className='mb-2'>
AƱadir elemento
</Button>
<Canva>
{notes && (notes.map(note => {
return (
<Note key={note._id} note={note} />
)
}))}
</Canva>
</Col>
</Row>
</Container>
</>
);
};
export default Dashboard;
If I called that function that way, my state doesn't change:
notes: undefined
But if I introduce a dependency inside of useEffect, my app goes into an infinite loop. For example:
useEffect(() => {
getNotes();
}, [notes])
//Or:
useEffect(() => {
getNotes()
}, [getNotes])
How can I avoid the infinite loop?
You need to use 2 useEffect hooks, one for fetch data and second to proceed it:
useEffect(() => {
getNotes();
}, []);
useEffect(() => {
if (notes && notes.length) {
....setState or what else
}
}, [notes]);
My note state looks like:
import React, { useReducer } from 'react';
import clientAxios from '../../config/clientAxios';
import NoteContext from './noteContext';
import NoteReducer from './noteReducer';
import {
GET_NOTES,
ADD_NOTE,
DELETE_NOTE,
UPDATE_NOTE,
} from '../../types';
const NoteState = ({ children }) => {
const initialState = {
notes: [],
noteError: false,
};
const [state, dispatch] = useReducer(NoteReducer, initialState);
const getNotes = async () => {
try {
const response = await clientAxios.get('/user/Notes');
dispatch({
type: GET_NOTES,
payload: response.data
})
} catch (error) {
console.log(error.response);
}
}
const addNote = async data => {
try {
const response = await clientAxios.post('/addNote', data);
dispatch({
type: ADD_NOTE,
payload: response.data.data
})
} catch (error) {
console.log(error.response);
}
}
const updateNote = async (id, { title, description }) => {
try {
const response = await clientAxios.put(`updateNote/${id}`, { title, description });
console.log(response.data);
dispatch({
type: UPDATE_NOTE,
payload: response.data
})
} catch (error) {
console.log(error.response)
}
}
const deleteNote = async id => {
try {
await clientAxios.put(`/deleteNote/${id}`);
dispatch({
type: DELETE_NOTE,
payload: id
})
} catch (error) {
console.log(error.response);
}
}
return(
<NoteContext.Provider
value={{
notes: state.notes,
noteError: state.noteError,
getNotes,
addNote,
updateNote,
deleteNote,
}}
>
{children}
</NoteContext.Provider>
);
}
export default NoteState;
and my reducer:
import {
GET_NOTES,
ADD_NOTE,
DELETE_NOTE,
UPDATE_NOTE,
} from '../../types';
export default (action, state) => {
switch(action.type) {
case GET_NOTES:
return {
...state,
notes: action.payload
}
case ADD_NOTE:
return {
...state,
notes: [...state.notes, action.payload]
}
case UPDATE_NOTE:
return {
...state,
notes: state.notes.map(note => note._id === action.payload._id ? action.payload : note)
}
case DELETE_NOTE:
return {
...state,
notes: state.notes.filter(note => note._id !== action.payload),
}
default:
return state;
}
}