After passing the product ID I'm being redirected to the product details page. In that page, I have details of the product such as name, destination, price and date etc. As I'm using redux and getting the data from mongodb the date is saved as 2021-11-23T18:00:00.000Z this format. So after fetching the data in my product details page I used flight?.data?.date.split("T")[0] to only show the date. But after clicking the button to go to the product details page it shows the following error TypeError: Cannot read properties of undefined (reading 'split'). But when I remove the split part from the code it is working fine. I guess the only reason is the code is running before the data is loaded. So for that I tried to show loading spinner before the data loads by dispatching loading action. But it isn't working. I've used redux thunk for other actions that is why I've added async in Action. Otherwise it gives me dispatch is not defined.
Details page
import { CircularProgress } from "#mui/material";
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import { getFlight, loadingStatus } from "../../actions/flights";
import "./Details.css";
const FlightDetails = () => {
const flight = useSelector((state) => state.flights);
console.log("flight", flight.loading);
const dispatch = useDispatch();
const { id } = useParams();
useEffect(() => {
dispatch(loadingStatus(true));
dispatch(getFlight(id));
dispatch(loadingStatus(false));
}, [id, dispatch]);
return (
<div className="details_parent">
{flight.loading ? (
<CircularProgress />
) : (
<div className="details_container">
<div className="form_data">
<span>From</span>
<h3>{flight?.data?.from}</h3>
</div>
<div className="form_data">
<span>To</span>
<h3>{flight?.data?.to}</h3>
</div>
<div className="form_data">
<span>Airline</span>
<h3>{flight?.data?.airline}</h3>
</div>
<div className="form_data">
<span>Trip type</span>
<h3>{flight?.data?.flightType}</h3>
</div>
<div className="form_data">
<span>Date(yyyy-mm-dd)</span>
<h3>{flight?.data?.date.split("T")[0]}</h3>
</div>
<div className="form_data">
<span>Price</span>
<h1>${flight?.data?.price}</h1>
</div>
</div>
)}
</div>
);
};
export default FlightDetails;
Reducer
const initialState = {
data: [],
loading: false,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "GET_FLIGHTS":
return { ...state, data: action.payload };
case "GET_FLIGHT":
return { ...state, data: action.payload };
case "CREATE_FLIGHT":
return { ...state, data: { ...action.payload } };
case "SEARCH":
return { ...state, data: action.payload };
case "LOADING":
return { ...state, loading: action.payload };
default:
return state;
}
};
export default reducer;
Actions
export const loadingStatus = (status) => async (dispatch) => {
dispatch({
type: "LOADING",
payload: status,
});
};
React runs a first render before running any useEffect calls. Change your initial loading state to true.
const initialState = {
data: [],
loading: true,
};
why you are not using flight?.data?.date?.split("T")[0]
but in this case you have to set loading in initial data of reducer to true
const initialState = {
data: [],
loading: true,
};
Rather than setting separate actions for change the loading state I've set the dispatching before getting the data in actions. And after the promise is returned I've again set the dispatch to false.
Actions to get the specific flight data
export const getFlight = (id) => async (dispatch) => {
try {
dispatch({
type: "LOADING",
payload: true,
});
const { data } = await api.fetchFlight(id);
dispatch({
type: "GET_FLIGHT",
payload: data,
});
dispatch({
type: "LOADING",
payload: false,
});
} catch (error) {
console.log(error);
}
};
Related
I created a custom useFetch() hook so I can make my code more dynamic and less repetitive. The problem is that I can't display my data in App.js.
I get these errors:
Cannot read properties of undefined (reading 'map').
react-dom.development.js:67 Warning: Can't perform a React state
update on an unmounted component. This is a no-op, but it indicates a
memory leak in your application. To fix, cancel all subscriptions and
asynchronous tasks in a useEffect cleanup function.
I did a console.log(genres) to see if there are any errors from my custom hook, but it works fine, logs all the genres. The problem is caused as soon as I try to display my data using the map method.
CodeSandbox link
useFetch.js
import { useReducer, useEffect } from "react";
import axios from "axios";
const ACTIONS = {
API_REQUEST: "api-request",
FETCH_DATA: "fetch-data",
ERROR: "error",
};
const initialState = {
data: [],
loading: false,
error: null,
};
function reducer(state, { type, payload }) {
console.log(payload);
switch (type) {
case ACTIONS.API_REQUEST:
return { ...state, data: [], loading: true };
case ACTIONS.FETCH_DATA:
return { ...state, data: payload, loading: false };
case ACTIONS.ERROR:
return { ...state, data: [], error: payload };
default:
return state;
}
}
function useFetch(url) {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({ type: ACTIONS.API_REQUEST });
axios
.get(url)
.then((res) => {
dispatch({ type: ACTIONS.FETCH_DATA, payload: res.data });
})
.catch((e) => {
dispatch({ type: ACTIONS.ERROR, payload: e.error });
});
}, [url]);
return state;
}
export default useFetch;
App.js
import "./styles.css";
import useFetch from "./useFetch";
export default function App() {
const BASE_URL =
"https://api.themoviedb.org/3/genre/movie/list?api_key=${API_KEY}";
const { data: genres, loading, error } = useFetch(BASE_URL);
console.log(genres);
return (
<div className="App">
{genres.genres.map((genre) => (
<div key={genre.id}>{genre.name}</div>
))}
</div>
);
}
Your initial state has data as an array:
const initialState = {
data: [],
loading: false,
error: null,
};
And your App component is trying to read the property genres on that array as soon as it loads. There is no property on an array with that name, so genres.genres is undefined, and the map call on it will throw an error.
I would initialise initialState.data as {genres: []}, by passing the data container as another argument to your hook rather than hardcoding it into the hook file.
function useFetch(url, data) {
const [state, dispatch] = useReducer(reducer, {...initialState, data});
...
}
const { data: genres, loading, error } = useFetch(BASE_URL, {genres: []});
I'm trying to render the data from the following object of data which is coming from an API.
{
"code": 0,
"c": "verified",
"d": "verified",
"leaseInfo": {
"infoId": 6
},
"cpfPrice": "500.00",
"carCurrentLocation": {
"id": 1,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
"n": "verified",
"p": "false",
"ownerCarInfo": {
"brand": "Ferrari",
"model": "0"
},
"serviceFeeRate": 0.10,
"depositPrice": "100.00",
"pics": [
{
"picid": 49,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
],
"items": {
"itemid": 5,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
}
}
I'm using react-redux to dispatch an action, where I will be provided with the data under a state named 'carDetails'.
However, when I try to access the data, if my component is refreshed, carDetails becomes undefined and hence gives "Cannot read property ownerCarInfo of undefined."
I'm obtaining and de-structuring the data of carDetails like this in my React component:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(match.params.id));
}, [dispatch, match]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
);
)
}
As long as the component or the React application is not refreshed, it retrieves data and displays it correctly. The carDetails becomes an empty array as soon as the page is refreshed.
This is the getCarDetails() action:
export const getCarDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: CAR_DETAILS_REQUEST,
});
const { userLogin } = getState();
const { userInfo } = userLogin;
const config = {
headers: {
Authorization: userInfo.token,
'Content-Type': 'application/json',
},
};
const { data } = await axios.get(
`${BASE_API}/car/info/getDetails/${id}/${userInfo.bscId}`,
config
);
dispatch({
type: CAR_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CAR_DETAILS_FAIL,
payload:
error.response && error.response.data.msg
? error.response.data.msg
: error.msg,
});
}
};
This is my reducer:
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { loading: true };
case CAR_DETAILS_SUCCESS:
return { loading: false, carDetails: action.payload };
case CAR_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
This is how I declare carDetails in the redux store.
const reducer = combineReducers({
carDetails: carsDetailsReducer,
});
What is the cause for carDetails becoming undefined and the useEffect not running on page refresh?
If you are using axios your action should look like this with async function and await while you are calling API.
If you are passing API car id in the api link then pass the id in the parameters:
import axios from "axios";
export const loadData = (id) => async (dispatch) => {
dispatch({
type: "CAR_DETAILS_REQUEST",
});
const detailData = await axios.get("http:\\****/id");
dispatch({
type: "CAR_DETAILS_SUCCESS",
payload: {
success: detailData.data,
},
});
};
Reducer:
const initailState = { carDetails: [], loading: true };
export const carsDetailsReducer = (state = initailState, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { ...state,
loading: true
};
case CAR_DETAILS_SUCCESS:
return {...state,
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return { ...state,
loading: false,
error: action.payload };
default:
return ...state;
}
};
Your useEffect should only work when data is fetched:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(id));
}, [dispatch]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
You can also use it without a useEffect by making an onclick() function like this:
const loadDetailHandler = () => {
dispatch(getCarDetails(id));
};
return (
<div onClick={loadDetailHandler} >
</div>
If carDetails initial state is an array, then why are you destructuring object properties from it in your UI? Question for another time...
If after reloading the page the state reverts back to the initial state, an empty array is still a defined object. You need to track down what is causing your state.carDetails.carDetails to become undefined. If you examine your reducer notice that your CAR_DETAILS_REQUEST case wipes the carDetails state out and it becomes undefined. Honestly I'm surprised you aren't seeing this issue when your code runs normally without a page reload.
You need to hold on to that state. For good measure, you should always shallow copy the existing state when computing the next state object unless you've good reason to omit parts of state.
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
case CAR_DETAILS_SUCCESS:
return {
...state, // <-- shallow copy existing state
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return {
...state, // <-- shallow copy existing state
loading: false,
error: action.payload,
};
default:
return state;
}
};
for me, I think you should save the state in the
`case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
`
to be able to use it before o when you want to use a reducer you should each case
have the old state the reducer return the same sharp of initial state that put it you also used is loading and that not found in the initial state
so try to make the shape of the state
state={
isloading:false,
carDetails: []
}
also try each time to same the state by {...state ,is loading:true}
The problem is in CAR_DETAILS_REQUEST. You only return { loading: true }; so carDetails will be lost and become undefined.
Just update your reducer like this:
case CAR_DETAILS_REQUEST:
return { ...state, loading: true };
I'm using hooks to get access to data and if I console.log the first level of data, I get everything including the objects nested in the initial object. If I try to access any of the data, it is returned undefined.
Component accessing data
import React, { useContext, useEffect } from 'react';
import { GlobalContext } from '../contexts/GlobalState';
export default function BlogDetail(props) {
const { blogPostToEdit, getBlogWithId } = useContext(GlobalContext);
useEffect(() => {
getBlogWithId(props.match.params.id)
}, [])
const { title, content, tags, date, userId } = blogPostToEdit;
const parsedDate = new Date(date);
console.log(blogPostToEdit)
console.log(userId);
return (
<div className='blog-detail-wrapper'>
<div className='blog-title'>
{title}
</div>
<div className='blog-detail-date'>
<div>{parsedDate.getMonth() + 1 + '/' + parsedDate.getDate() + '/' + parsedDate.getFullYear()}</div>
</div>
<div className='blog-content'>
{content}
</div>
<div className='blog-tags'>
{tags}
</div>
</div>
)
Context
import React, { createContext, useReducer } from 'react';
import axios from 'axios';
import BlogReducer from '../reducers/BlogReducer';
const initialState = {
blogPosts: [],
blogPostToEdit: {},
error: null
}
export const GlobalContext = createContext(initialState);
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(BlogReducer, initialState);
async function getBlogPosts() {
try {
const res = await axios.get('http://localhost:4000/api/blogPost/getPosts');
dispatch({
type: 'GET_BLOGS',
payload: res.data
})
} catch (err) {
dispatch({
type: 'FETCH_ERROR',
payload: err.resposonse.data.error
})
}
}
async function getBlogWithId(id) {
try {
const res = await axios.get(`http://localhost:4000/api/blogPost/getPostById/${id}`);
dispatch({
type: 'GET_BLOG_ID',
payload: res.data
})
} catch (err) {
dispatch({
type: 'FETCH_ERROR',
payload: err.resposonse.data.error
})
}
}
return (
<GlobalContext.Provider value={{
blogPosts: state.blogPosts,
blogPostToEdit: state.blogPostToEdit,
auth: state.auth,
error: state.error,
getBlogPosts,
getBlogWithId
}}>
{children}
</GlobalContext.Provider>
)
}
Reducer
export default (state, action) => {
switch (action.type) {
case 'GET_BLOGS':
return {
...state,
blogPosts: action.payload
}
case 'GET_BLOG_ID':
return {
...state,
blogPostToEdit: action.payload
}
default: return state;
}
}
This is what the data looks like
blogPostToEdit:{
content: "Right now I'm all stressing it out because I can't seem to know how to work with state and..."
date: "2020-01-02T01:18:04.488Z"
tags: ["RouterLink, Javascript, react, dummydumtime"]
title: "This is whole new blog for a whole new year"
userId:{
date: "2019-08-27T23:27:42.945Z"
email: "gloob#email.com"
name: "gloob"
password: "***"
_id: "5d65bc6e4402213647f8704b"
}
_id: "5e0d44cc5ba6ddb32418c760"
}
What I'm trying to do is get access to the name and but it's returned undefined when I try to use it. This could be a repeated question but I can't seem to find the answer. Thanks
I'm trying to create a loading state for my Redux but it looks to "slow" to get updated.
First action fetchDB => setLoading: true => once over setLoading: false
Second action fetchCat => doesn't have the time to fire it that crashes
Really simple:
set loading action:
export const setLoading = () => {
return async (dispatch) => {
await dispatch({ type: SET_LOADING }); // no payload by default goes to true
};
};
set loading reducer:
import {
FETCH_DB,
SET_LOADING,
} from "../types"
const initalState = {
db: [],
loading: false,
}
export default (state = initalState, action) => {
switch (action.type) {
// this like the other cases sets loading to FALSE
case FETCH_DB:
return {
...state,
db: action.payload,
current: null,
loading: false,
}
case FETCH_CAT_FOOD:
return {
...state,
food: action.payload,
loading: false,
}
case FETCH_CAT_DESIGN:
return {
...state,
design: action.payload,
loading: false,
}
case SET_LOADING:
return {
...state,
loading: true,
}
default:
return state
}
}
then action I use that creates the problem:
export const fetchCat = kindof => {
return async dispatch => {
dispatch(setLoading()) // looks like that it doesn't get fired
const response = await axios
.get(`http://localhost:5000/api/categories/${kindof}`)
.then(results => results.data)
try {
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
and then the file (a custom component) that creates the problem.
It crashes cause categories.map is undefined.
It doesn't find loading: true so the loader doesn't stop.
import React, { useState, useEffect, Fragment } from "react"
import { Spinner } from "react-bootstrap"
import { connect, useDispatch, useSelector } from "react-redux"
import CatItem from "./CatItem" // custom component
import { fetchCat, setLoading } from "../../../store/actions/appActions"
const MapCat = ({ kindof, loading, categories }) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchCat(kindof)) // gives the category I want to fetch
// eslint-disable-next-line
}, [categories])
if (!loading) {
return (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)
} else {
return (
<Fragment>
<div>
{categories.map(item => (
<CatItem item={item} />
))}
</div>
</Fragment>
)
}
}
const mapStateToProps = (state, kindof) =>
({
loading: state.appDb.loading,
categories: state.appDb[kindof],
})
export default connect(mapStateToProps, { fetchCat, setLoading })(MapCat)
I think that it is supposed to work like this:
loading: false (by default) => true => time to fetch => false
But doesn't look like working. Any idea?
Firstly setLoading needs to return a plain object with type and payload
export const setLoading = () => ({ type: SET_LOADING });
In fetchCat the then is not required. Also async await for dispatch is not required.
export const fetchCat = (kindof) => {
return (dispatch) => {
dispatch(setLoading()); //<---this should now be ok.
const response = await axios.get(`http://localhost:5000/api/categories/${kindof}`)
// .then((results) => results.data); //<----- not required as you are using await
try {
dispatch({ type: `FETCH_CAT_${kindof}`, payload: response.data }); //<--- use response.data ...also async/await for dispatch is not rquired.
} catch (error) {
console.log("await error", error);
}
};
};
The 2nd arg of mapStateToProps is ownProps which is an object
const mapStateToProps = (state, ownProps) =>
({
loading: state.appDb.loading,
categories: state.appDb[ownProps.kindof],
})
You have quite a bit different way of calling dispatch. Let me list them out
dispatch(fetchCat(kindof)) // gives the category I want to fetch
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
You can see, await or not basically is the way you use async operation. However dispatch takes type and payload to function, which means you have to make sure what you send to dispatch is with the right object. Of course Redux does accept custom format via plugins, so maybe if you throw it a async as input, the reducer might understand it as well?
Please double check each dispatch first, for example, write a function that only dispatch one type of action. Only after you make each call working, don't move to assemble them together into a bundled call.
I've created three useSelector hooks and for the first one I change the value by dispatching an action with an OnClick function. When I do this my other useSelects get re-rendered even though the reference hasn't changed. Does anyone know why this is happening? I believe this is happening because I put console.logs inside the useSelectors and see them get fired off every time I click the button. minedDiamond should be the only value to change when I click the button.
Minecraft.js
import React, { useEffect, useCallback } from "react";
import { connect, useSelector, useDispatch, shallowEqual } from "react-redux";
import { mineDiamond, fetchMinecraftItems } from "../redux/diamonds/actions";
const Minecraft = () => {
const loading = useSelector((state) => {
console.log("loading output");
return state.diamond.loading;
});
let minedDiamond = useSelector((state) => {
console.log("diamond output");
return state.diamond.minedDiamond;
});
const names = useSelector((state) => {
console.log("name rendered");
let data = state.diamond.minecraftData;
return data.map((i) => i.name);
});
const dispatch = useDispatch();
const handleClick = () => dispatch(mineDiamond((minedDiamond += 1)));
useEffect(() => {
dispatch(fetchMinecraftItems());
}, []);
console.log({ loading });
return (
<div className="wrapper">
<div className="wrapper__item">
<img src="/image/pickaxe.png" alt="diamond" />
<button onClick={handleClick} type="button" className="wrapper__button">
Mine
<span role="img" aria-label="cart">
🛒
</span>
</button>
</div>
<div className="wrapper__item">
<img src="/image/diamond.png" alt="axe" />
<span className="num">{minedDiamond}</span>
</div>
<div className="num">
{loading ? (
<p>loading...</p>
) : (
<h1 className="num">{names}</h1>
)}
</div>
</div>
);
};
export default Minecraft;
Action Creators
import * as Actions from "./actionTypes";
import axios from "axios";
//action creator
export const mineDiamond = (addDiamond) => ({
type: Actions.MINE_DIAMOND,
payload: addDiamond,
});
export function fetchMinecraftItems() {
return function (dispatch) {
dispatch({ type: Actions.MINECRAFT_DATA_FETCH });
return fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((json) => {
dispatch({ type: Actions.MINECRAFT_DATA_SUCCESS, payload: json });
})
.catch((err) =>
dispatch({ type: Actions.MINECRAFT_DATA_FAIL, payload: err })
);
};
}
Reducer
import * as Actions from "./actionTypes";
//reducer holds initial state
const initialState = {
minedDiamond: 0,
minecraftData: [],
loading: false,
error: null,
};
//reducer
const diamondReducer = (state = initialState, action) => {
switch (action.type) {
case Actions.MINE_DIAMOND:
return {
...state,
minedDiamond: action.payload,
};
case Actions.MINECRAFT_DATA_FETCH:
return {
...state,
loading: true,
};
case Actions.MINECRAFT_DATA_SUCCESS:
return {
...state,
loading: false,
minecraftData: action.payload,
};
case Actions.MINECRAFT_DATA_FAIL:
return {
...state,
error: action.payload,
};
default:
return state;
}
};
export default diamondReducer;
Your issue has nothing to do with the usage of console.log.
Would you kindly include the code for your action creators and reducers?
Assuming state.diamond.mincraftdata is an array, the line
return data.map((i) => i.name);
creates a new array every time the selector is run. As per the react-redux docs:
when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison.