Issue in react component - reactjs

I have a problem in the following component, it seems that the component doesn't render and I get the following error in console: "Cannot read property 'operationalHours' of null". I don't get why operationalHours it's null.. maybe someone can help me with a posible solution for this issue.
Here is the component:
import React, { useState, useEffect } from 'react';
import Search from 'client/components/ui/Search';
import { performSearchById } from 'client/actions/api/search';
import { get } from 'lodash';
import {
SEARCH_STORE_NOT_CLOSED,
SEARCH_STORE_OPEN_TEXT,
SEARCH_STORE_CLOSED_TEXT
} from 'app/client/constants/values';
import DownArrow from 'components/UI/icons/DownArrow';
import styles from './styles.module.scss';
const StoreDetails = ({ storeInfo }) => {
const [expanded, setIsExpanded] = useState(false);
const [storeData, setStoreData] = useState(null);
useEffect(() => {
async function fetchData() {
const storeId = storeInfo.store_id;
const {
data: {
Location: {
contactDetails: { phone },
operationalHours
}
}
} = await performSearchById(storeId);
setStoreData({ phone, operationalHours });
}
fetchData();
}, [storeInfo.store_id]);
const infoText = expanded ? 'Hide details' : 'View details';
function parseHours(hours) {
const formattedHours = {};
hours.forEach(dayObj => {
const closed = get(dayObj, 'closed', '');
const day = get(dayObj, 'day', '');
if (closed === SEARCH_STORE_NOT_CLOSED) {
const openTime = get(dayObj, 'openTime', '');
const closeTime = get(dayObj, 'closeTime', '');
if (openTime === null || closeTime === null) {
formattedHours[day] = SEARCH_STORE_OPEN_TEXT;
} else {
formattedHours[day] = `${openTime}-${closeTime}`;
}
} else {
formattedHours[day] = SEARCH_STORE_CLOSED_TEXT;
}
});
return formattedHours;
}
const storeHours = storeData.operationalHours
? parseStoreHours(storeData.operationalHours)
: '';
return (
<div className={styles.viewStoreDetails}>
<span
className={expanded ? styles.expanded : undefined}
onClick={() => setIsExpanded(!expanded)}
>
<DownArrow />
</span>
<div>
<span className={styles.viewStoreDetailsLabel}>{infoText}</span>
<div>
{expanded && (
<Search
phoneNumber={storeData.phone}
storeHours={storeHours}
/>
)}
</div>
</div>
</div>
);
};
export default StoreDetails;

Its because you're setting the values of storeData after the component has already rendered the first time. Your default value for storeData is null.
It breaks here: storeData.operationalHours because null isn't an object and therefore cannot have properties to access on it.
You should probably just set your initial state to something more representative of your actual state:
const [storeData, setStoreData] = useState({}); // Or even add keys to the object.
Also read here about the useEffect hook and when it runs. It seems that the underlying issue is misunderstanding when your data will be populated.

You are getting error at this line :
const storeHours = storeData.operationalHours ?
parseStoreHours(storeData.operationalHours): '';
Reason : You initialised storeData as Null and you are trying to access operationalHours key from Null value.
Correct Way is :
Option 1: Initialise storeData as blank object
const [storeData, setStoreData] = useState({});
Option 2:
const storeHours =storeData && storeData.operationalHours ?
parseStoreHours(storeData.operationalHours): '';

It's happen because in 1st moment of your application, storeData is null, and null don't have properties, try add a empty object as first value ({}) or access a value like that:
Correct method:
const object = null;
console.log(object?.myProperty);
// output: undefined
Wrong method:
const object = null;
console.log(object.myProperty);
// Generate a error
The Question Mark(?) is a method to hidden or ignore if the variable are a non-object, to decrease verbosity in the code with logic blocks try and catch, in Correct method code there will be no mistake, but in Wrong method code, there will have a mistake.
Edit 1:
See more here

Related

Error Handling array.map is not a function

Sometimes the app fails to connect to the database and retrieve the data I want. When this happens I get an error back saying "cannot read properties of undefined (reading 'map')."
I have tried to use a '?' so it only maps when the length of the array is greater than 0, i.e not empty. However I don't think this is working currently...
I want to handle this error without the page crashing. Any advice would be appreicated.
import axios from "axios";
import { Router, useRouter } from "next/router";
import { useEffect, useState } from "react";
function Class() {
const router = useRouter();
const classId = router.query.classId;
const yearId = router.query.yearId;
const weekId = router.query.weekId;
const [className, setClassname] = useState("");
const [cards, setCards] = useState<React.ReactElement[]>();
const [cardsForMatchingGame, setCardsForMatchingGame] = useState<React.ReactElement[]>();
const [flashcards, setFlashcards] = useState<React.ReactElement[]>();
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
if (!router.isReady) return;
if (router.isReady && className) {
const fetchAllFlashcards = async () => {
setIsError(false);
setIsLoading(true);
try {
const res = await axios.get(`/api/ClassSelector/${yearId}/${weekId}/${className}`);
setFlashcards(res.data);
} catch (err) {
setIsError(true);
}
setIsLoading(false);
};
fetchAllFlashcards();
}
}, [router.isReady, className, weekId, yearId, classId]);
useEffect(() => {
if (!router.isReady || flashcards?.length === 0) return;
if (router.isReady && flashcards?.length !== 0) {
const deck = flashcards.map((card) => {
const { id, english, japanese, example_sentence, week, year } = card;
return (
<div key={id + week + year + english} className="flex items-center justify-center">
<Flashcards
english={english}
japanese={japanese}
classPath={`https://eb-flashcards.vercel.app/ClassSelector/${yearId}/${weekId}/${className}`}
showDeleteButton={false}
/>
</div>
);
});
setCards(deck);
}
}, [router.isReady, flashcards, className, yearId, weekId])
return(<div>{cards}</div>)
}
Sometimes flashcards is null or undefined.
In javascript:
null?.length !== 0 and undefined?.length !== 0 are true.
So this condition:
if (router.isReady && flashcards?.length !== 0)
will be satisfied and js tries to call map method of flashcards which is null or undefined. Thus, the error occurs.
One way is to change the condition like so:
if (router.isReady && flashcards && flashcards?.length !== 0)
As mentioned before: if your res.data is null or undefined it will pass the condition. But instead of checking for it, I would recommend to set falshcards to [] if res.data is falsy (so something like this in your first useEffect:
try {
const res = await axios.get(`/api/ClassSelector/${yearId}/${weekId}/${className}`);
setFlashcards(res.data || []);
}...
Also you don't need your second useEffect or a state for cards. Is there any particular reason you want them?
Your code would look way better if you got rid of those and instead doing something like:
function Class() {
....
useEffect(() => {
...
}
}, [router.isReady, className, weekId, yearId, classId]);
....
if (!falshcards || flashcards?.length === 0) return null;
// Instead of second useEffect and the return(<div>{cards}</div>):
return (
<>
{flashcards.map((card) => {
const {id, english, japanese, example_sentence, week, year} = card;
return (
<div key={id + week + year + english} className="flex items-center justify-center">
<Flashcards
english={english}
japanese={japanese}
classPath={`https://eb-flashcards.vercel.app/ClassSelector/${yearId}/${weekId}/${className}`}
showDeleteButton={false}
/>
</div>
);
})}
</>
);
}
You can check before iteration on flashcards.
const result = Array.isArray(arr) ? arr.map(element => element + 1) : [];

Redux Toolkit, component does not re-render after RTK query is fulfilled

I have a child component like so:
import { skipToken } from "#reduxjs/toolkit/dist/query";
import moment from "moment";
import {
useFetchAccountQuery,
useFetchTransactionsQuery,
} from "./accountApiSlice";
const TransactionsTable = () => {
const { data: accountData } = useFetchAccountQuery();
const changesSince: string = moment().subtract(7, "d").toISOString();
let params = {
accountId: accountData?.accountId,
categoryId: accountData?.defaultCategoryId,
changesSince,
};
const paramsAreGood: boolean = params.accountId && params.categoryId ? true : false;
const { data: transactions, isSuccess: transactionsSuccess } = useFetchTransactionsQuery(paramsAreGood ? params : skipToken);
return (
<>
{transactionsSuccess && (
<div>
<h1>Number of transactions: {transactions?.length}</h1>
</div>
)}
</>
);
};
export default TransactionsTable;
I am making two queries, then using the accountId and categoryId from the 1st query to pass to the 2nd query so that I can make the transactions query and get the data.
In Chrome's Network tab, I can see that my useFetchTransactionsQuery() fires and I get data back.
However, my component remains blank and I do not get:
<h1>Number of transactions: <some number></h1>
Here is a screenshot from Redux Dev Tools too:
What am I missing?
I think before you set values to param you should check if useFetchAccountQuery has returned any data yet, as useFetchAccountQuery is an async function and i think the param value is set before account data is fatched
try it like this
import { skipToken } from "#reduxjs/toolkit/dist/query";
import moment from "moment";
import {
useFetchAccountQuery,
useFetchTransactionsQuery,
} from "./accountApiSlice";
const TransactionsTable = () => {
const { data: accountData, isSucceess: AccountDataSuccess } = useFetchAccountQuery();
const changesSince: string = moment().subtract(7, "d").toISOString();
let params ;
if(AccountDataSuccess && accountData ){
params = {
accountId: accountData?.accountId,
categoryId: accountData?.defaultCategoryId,
changesSince,
}
};
const paramsAreGood: boolean = params.accountId && params.categoryId ? true : false;
const { data: transactions, isSuccess: transactionsSuccess } = useFetchTransactionsQuery(paramsAreGood ? params : skipToken);
return (
<>
{transactionsSuccess && (
<div>
<h1>Number of transactions: {transactions?.length}</h1>
</div>
)}
</>
);
};
export default TransactionsTable;
Please annotate the param accordingly if your are using typescript.
I hope this works. I use this type of pattern in my work and it works.
The culprit was this line:
const changesSince: string = moment().subtract(7, "d").toISOString();
I've no idea why, but I ditched moment for dayjs and it works now.

After copying array, why can't I edit nested array?

I'm trying to edit an array by removing a specific date. I'm using React18 and Redux Toolkit holds the original array, but for some reason after copying it, I cannot edit the array. Here is the current error message;
"Uncaught TypeError: Cannot assign to read only property 'dates' of object '#'"
What is wrong with my approach?
import { useDispatch, useSelector } from "react-redux";
import { setCurrentMonthBookings } from "./location";
const Component = () => {
const { booking, currentMonthBookings } = useSelector(state => state.calendar);
const handleDelete = () => {
let reservations = currentMonthBookings.slice();
const bookingIndex = reservations.findIndex(
(curBooking) =>
curBooking.date === booking.date && curBooking.id === booking.id,
);
const newDates = reservations[bookingIndex].dates.filter(
(date) => date !== booking.date,
);
reservations.splice(bookingIndex, 1);
reservations.forEach((reservation) => {
if (reservation.id === booking.id) {
reservation.dates = newDates; //error happens here...
}
});
dispatch(setCurrentMonthBookings(reservations));
}
return (
<div>
<button onClick={handleDelete}>Delete It</button>
</div>
);
}
export default Component;
What the booking object looks like...
{
date: "2022-05-03",
dates: (2) ['2022-05-03', '2022-05-04'],
guestId: "1938479385798579",
id: "9879287498765"
}
The currentMonthBookings array is a series of booking objects.
Thank you for your replies.

React Redux Type Error: Cannot read properties of undefined (reading 'comment')

I'm new to React building a simple app and I get this error when I'm trying to add a comment to a photo, the code is working and the state is changing correctly when I hit enter
I the error in this line
const photo = photos.find((item) => item.id === Number(photo_id));
the photos are defined and the id is defined but I get the photo is undefined
I really appreciate it if anyone could help
here's the code
import { useNavigate, useParams } from 'react-router-dom';
import { useSelector, connect } from 'react-redux'
import Photo from './Photo';
import { useState } from 'react';
import { addComment } from './actions'
const PhotoDetail = ({ addComment }) => {
const {photo_id} = useParams();
const navigate = useNavigate();
const [text, setText] = useState('');
const photos = useSelector((state) => state.photoList.photos);
const photo = photos.find((item) => item.id === Number(photo_id));
console.log('here photo id', photo_id)
console.log('here photo', photo)
console.log('here photos', photos)
const comments = photo['comment'];
const handelKeyDown = (e) => {
if (e.key === "Enter") {
const commentData = {text, photo_id}
addComment(commentData);
// navigate('/' + photo.id);
}
}
return (
<div className="detail">
<div className="photos photoDetail">
<Photo key={photo.id} photo={photo}/>
</div>
<div>
<h2>Comments</h2>
<div>
{ comments.map((comment) => (
<p key={comment.id}>{comment.text}</p>
)) }
</div>
<input type="text" value={text} onChange = {
(e) => setText(e.target.value)
} onKeyDown={
handelKeyDown
}/>
</div>
</div>
);
}
const mapDispatchToProps = dispatch => ({
addComment: commentData => dispatch(addComment(commentData))
})
export default connect(null, mapDispatchToProps) (PhotoDetail);
here's the action
export const addComment = (commentData) => {
console.log('test')
return {
type:"ADDCOMMENT",
payload: commentData
};
};
and here's the Reducer
case "ADDCOMMENT":
const idx = Math.floor(Math.random() * 10000) + 1;
const { text, photo_id } = action.payload;
const newComment = {idx, text}
return { ...state, photos:[state.photos.map((image) =>
image.id === photo_id ? image.comment.push(newComment) && image : image),] }
the console
the console
find will return undefined in case nothing matches the required condition.
So, looks like item.id === Number(photo_id) is probably not resolving to true for any photo in photos.
Then, you are trying to access comment on undefined, that's why there's a TypeError.
In action payload photo_id has string type and in reducer you have added === check but image.id holds number type.
Added, a comment in below code for your better understanding.
case "ADDCOMMENT":
const idx = Math.floor(Math.random() * 10000) + 1;
const { text, photo_id } = action.payload;
const newComment = {idx, text}
return { ...state, photos:[state.photos.map((image) =>
// HERE below condition fails always bcz of strict type check, to photo_id
//- either add Number convertion or in payload itself send string type
image.id === photo_id ? image.comment.push(newComment) && image : image),] }
thank you guys your answers were very helpful but I change the logic
I already have the photo I didn't need to map throw the photos so I just add the comment to the photo and return the state and it works!
case "ADDCOMMENT":
const idx = Math.floor(Math.random() * 10000) + 1;
const { text, photo } = action.payload;
console.log('the photo from reducer', photo)
const newComment = {idx, text};
photo.comment.push(newComment) // adding the comment to the photo
// return { ...state, photos:[state.photos.map((image) =>
// Number(image.id) === Number(photo.id) ? image.comment.push(newComment) && image : image),] }
return state;

Using document.cookie in Gatsby

I need to be able to set and access cookies in my Gatsby project, and I was able to get something solid setup using this tutorial. I'm building a hook that sets a cookie, and utilizing it throughout the site. This is what the helper looks like when it's all said and done.
use-cookie.ts
import { useState, useEffect } from 'react';
const getItem = (key) =>
document.cookie.split('; ').reduce((total, currentCookie) => {
const item = currentCookie.split('=');
const storedKey = item[0];
const storedValue = item[1];
return key === storedKey ? decodeURIComponent(storedValue) : total;
}, '');
const setItem = (key, value, numberOfDays) => {
const now = new Date();
// set the time to be now + numberOfDays
now.setTime(now.getTime() + numberOfDays * 60 * 60 * 24 * 1000);
document.cookie = `${key}=${value}; expires=${now.toUTCString()}; path=/`;
};
/**
*
* #param {String} key The key to store our data to
* #param {String} defaultValue The default value to return in case the cookie doesn't exist
*/
export const useCookie = (key, defaultValue) => {
const getCookie = () => getItem(key) || defaultValue;
const [cookie, setCookie] = useState(getCookie());
const updateCookie = (value, numberOfDays) => {
setCookie(value);
setItem(key, value, numberOfDays);
};
return [cookie, updateCookie];
};
I'm calling the hook into a component like so:
DealerList.tsx
import React, { ReactNode, useEffect } from 'react';
import { Container } from 'containers/container/Container';
import { Section } from 'containers/section/Section';
import { Link } from 'components/link/Link';
import s from './DealerList.scss';
import { useCookie } from 'hooks/use-cookie';
interface DealerListProps {
fetchedData: ReactNode;
}
let cookie;
useEffect(() => {
cookie = useCookie();
}, []);
export const DealerList = ({ fetchedData }: DealerListProps) => {
const dealerInfo = fetchedData;
if (!dealerInfo) return null;
const [cookie, updateCookie] = useCookie('one-day-location', 'sacramento-ca');
return (
<>
<Section>
<Container>
<div className={s.list}>
{dealerInfo.map((dealer: any) => (
<div className={s.dealer} key={dealer.id}>
<div className={s.dealer__info}>
<h3 className={s.name}>
{dealer.company.name}
</h3>
<span className={s.address}>{dealer.address.street}</span>
<span className={s.city}>{dealer.address.city} {dealer.address.zip}</span>
</div>
<div className={s.dealer__contact}>
<span className={s.email}>{dealer.email}</span>
<span className={s.phone}>{dealer.phone}</span>
</div>
<div className={s.dealer__select}>
<Link
to="/"
className={s.button}
onClick={() => {
updateCookie(dealer.phone, 10);
}}
>
Select Location
</Link>
</div>
</div>
))}
</div>
</Container>
</Section>
</>
);
};
It works well on gatsby develop and I'm able to access the value of the cookie and change the contact information that's displayed accordingly. However, when I try and build, or push to Netlify, I'm getting this error.
WebpackError: ReferenceError: document is not defined
I know this has something to do with document.cookie on lines 4 and 17, but I'm struggling trying to figure out how to fix it. Any suggestions? I'm imported useEffect, and from my research that has something to do with it, but what can I do to get it working properly?
Thanks in advance.
I did a bit more research, and I found this simple hook, replaced the code in use-cookie.ts with this, made a few modifications to it (included below), installed universal-cookie and it seems to work perfectly. Here's the new code:
use-cookie.ts
import { useState } from 'react';
import Cookies from 'universal-cookie';
export const useCookie = (key: string, value: string, options: any) => {
const cookies = new Cookies();
const [cookie, setCookie] = useState(() => {
if (cookies.get(key)) {
return cookies.get(key);
}
cookies.set(key, value, options);
});
const updateCookie = (value: string, options: any) => {
setCookie(value);
removeItem(value);
cookies.set(key, value, options);
};
const removeItem = (key: any) => {
cookies.remove(key);
};
return [cookie, updateCookie, removeItem];
};
If anyone has a better way to do this though, please let me know!
According to Gatsby's Debugging HTML Builds documentation:
Some of your code references “browser globals” like window or
document. If this is your problem you should see an error above like
“window is not defined”. To fix this, find the offending code and
either a) check before calling the code if window is defined so the
code doesn’t run while Gatsby is building (see code sample below) or
b) if the code is in the render function of a React.js component, move
that code into a componentDidMount lifecycle or into a useEffect hook,
which ensures the code doesn’t run unless it’s in the browser.
So, without breaking the rule of hooks, calling a hook inside another hook, causing a nested infinite loop. You need to ensure the document creation before calling it. Simply by adding a checking condition:
import { useState } from 'react';
const getItem = (key) => {
if (typeof document !== undefined) {
document.cookie.split(`; `).reduce((total, currentCookie) => {
const item = currentCookie.split(`=`);
const storedKey = item[0];
const storedValue = item[1];
return key === storedKey ? decodeURIComponent(storedValue) : total;
}, ``);
}
};
const setItem = (key, value, numberOfDays) => {
const now = new Date();
// set the time to be now + numberOfDays
now.setTime(now.getTime() + numberOfDays * 60 * 60 * 24 * 1000);
if (typeof document !== undefined) {
document.cookie = `${key}=${value}; expires=${now.toUTCString()}; path=/`;
}
};
/**
*
* #param {String} key The key to store our data to
* #param {String} defaultValue The default value to return in case the cookie doesn't exist
*/
export const useCookie = (key, defaultValue) => {
const getCookie = () => getItem(key) || defaultValue;
const [cookie, setCookie] = useState(getCookie());
const updateCookie = (value, numberOfDays) => {
setCookie(value);
setItem(key, value, numberOfDays);
};
return [cookie, updateCookie];
};
Since you might be calling useCookie custom hook in a component or page where the document doesn't exist yet, I would double-check by using the same condition or using a useEffect with empty dependencies ([], now it won't break the rule of hooks):
const Index = props => {
let cookie;
// both works
if (typeof document !== undefined) {
cookie = useCookie();
}
useEffect(() => {
cookie = useCookie();
}, []);
return <Layout>
<Seo title="Home" />
<h1>Hi people</h1>
</Layout>;
};

Resources