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) : [];
Related
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;
This is code and I am trying to access the result from the api but I am not able to show it on my page.
The results are visible in console but not in page.
I have tried few things but it hasn't worked for me.
I am stuck on this only for the last few days.
import React, { useState } from 'react'
const SearchArea = () => {
const [input,setInput] = useState("");
const [results,setResults] = useState(null)
const onInputChange = (ev) => {
setInput(ev.target.value)
}
const onSearch = () => {
fetch(`https://api.shrtco.de/v2/shorten?url=${input}`)
.then(r=>r.json())
.then(result=>{
setResults(result) ;
console.log(result.result.full_short_link)})
}
const onKeyDown= (ev) =>{
if(ev.keyCode === 13){
onSearch();
}
}
const renderResult = () => {
if(results && results.ok === 0){
return <div>No link to convert.</div>
}
if(results && results.length > 0){
return <div>{results.map((item)=><div>{item.results.result.full_short_link}</div>)}</div>
}
return null
}
return (
<div>
<div className="search-bar">
<input type="text" onKeyDown={onKeyDown} onChange={onInputChange} value={input} className="searching" placeholder="Shorten a link here..."></input>
<button className="short-butt" onClick={onSearch}>Shorten It!</button>
<div>{renderResult()}</div>
</div>
</div>
)
}
export default SearchArea
From what I can see, it seems that you are trying to show MULTIPLE results from the API. So you must start with an array instead of null in the state.
const [results, setResults] = useState([]);
Then for every response from the API, you could either push into the results with results.push() (not recommended) or you could do spread operator like below (more recommended):
fetch(`https://api.shrtco.de/v2/shorten?url=${input}`)
.then((r) => {
return r.json();
})
.then((result) => {
console.log(result.result.full_short_link);
setResults([...results, result.result.full_short_link]); //spread operator
})
.catch((e) => {
console.error(e);
});
Later you can use map on showing the results.
if(results && results.length > 0){
return <div>{results.map((item)=><div>{item.results.result.full_short_link}</div>)}</div>
}
Result:
You can see the code in action: https://codesandbox.io/s/react-link-shorter-7ro9o?file=/index.js
I've got the following code:
export default function App() {
const [lastMessageId, setLastMessageId] = useState(0);
const [messages, setMessages] = useState([]);
const addMessage = (body, type) => {
const newMessage = {
id: lastMessageId + 1,
type: type,
body: body,
};
setLastMessageId(newMessage.id)
setMessages([...messages, newMessage]);
console.log("point 1", messages);
return newMessage.id;
}
// remove a message with id
const removeMessage = (id) => {
const filter = messages.filter(m => m.id !== id);
console.log("point 2", filter);
setMessages(filter);
}
// add a new message and then remove it after some seconds
const addMessageWithTimer = (body, type="is-primary", seconds=5) => {
const id = addMessage(body, type);
setTimeout(() => removeMessage(id), seconds*1000);
};
return (
...
);
}
I would like to know why after I setMessages at point 1, when I do console log it doesn't appear to be updated. This turns into a weird behaviour when I call addMessageWithTimer because when it calls removeMessage then it doesn't remove correctly the messages that I expect.
Could you please explain me how to do it?
Just like setState in class-components, the update functions of useState don't immediately update state, they schedule state to be updated.
When you call setMessages it causes react to schedule a new render of App which will execute the App function again, and useState will return the new value of messages.
And if you think about it from a pure JS perspective, messages can't change: it's just a local variable, (a const one, even). Calling a non-local function can't cause a local variable's value to change, JS just doesn't work that way.
#Retsam is correct in his explanation.
I think you would get an issue if you don't use setTimeout in addMessageWithTimer. Isn't it? But for now, it is correct.
If you don't want to give a timer of 5 seconds and still want to keep it running correctly, then give a timer of 0 seconds. It would still work okay.
what weird behavior your seeing?
when I tried your code, I'm able to remove the added message after 5 sec.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
let bodyText = "";
const [lastMessageId, setLastMessageId] = useState(0);
const [messages, setMessages] = useState([]);
const addMessage = (body, type) => {
if (body === "") return;
const newMessage = {
id: lastMessageId + 1,
type: type,
body: body
};
setLastMessageId(newMessage.id);
setMessages([...messages, newMessage]);
bodyText = "";
return newMessage.id;
};
// remove a message with id
const removeMessage = (id) => {
const filter = messages.filter((m) => m.id !== id);
console.log("point 2", filter);
setMessages(filter);
};
// add a new message and then remove it after some seconds
const addMessageWithTimer = (body, type = "is-primary", seconds = 5) => {
const id = addMessage(body, type);
setTimeout(() => removeMessage(id), seconds * 1000);
};
console.log("point 1", messages);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input onChange={(e) => (bodyText = e.target.value)} />
<button onClick={(e) => addMessage(bodyText, "is-primary")}>
Add messsage
</button>
<button onClick={(e) => addMessageWithTimer(bodyText, "is-primary", 5)}>
Add temp messsage
</button>
{messages.map((message, id) => {
return (
<div key={id}>
<p>
{message.id} {message.body}
</p>
</div>
);
})}
</div>
);
}
#Retsam was very useful with his answer as I was able to understand the problem and find a proper solution.
here is the solution that I've found:
export default function App() {
const [lastMessageId, setLastMessageId] = useState(0);
const [messages, setMessages] = useState([]);
const addMessage = (body, type="is-primary") => {
const newMessage = {
id: lastMessageId + 1,
type: type,
body: body
};
setLastMessageId(newMessage.id)
setMessages([...messages, newMessage]);
return newMessage.id;
}
// delete messages after 5 seconds
useEffect(() => {
if (!messages.length) return;
const timer = setTimeout(() => {
const remainingMessages = [...messages];
remainingMessages.shift();
setMessages(remainingMessages);
}, 5*1000);
return () => clearTimeout(timer);
}, [messages]);
return (
...
);
}
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
Hello I have created a search bar with a multipl filter, it works but the functions are too dependent on each other. The problem here is that the functions are handling multiple cases.
would it be possible to lighten each function by chaining them and how ? I don't really get chaining method.
thanks
import React, { useState, useEffect } from "react";
import Search from "./Search";
import Anime from "./Anime";
import "./App.css";
const KIJAN_API_URL = "https://api.jikan.moe/v3/top/anime/1/upcoming";
const App = () => {
const [animes, setAnimes] = useState([]);
const [sortedAnimes, setSortedAnimes] = useState([]);
const [searchValue, setSearchValue] = useState("")
const [filterByType, setFilterByType] = useState("");
const [filterByYear, setFilterByYear] = useState("");
useEffect(() => {
fetch(KIJAN_API_URL)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Something went wrong");
}
})
.then(jsonResponse => {
setAnimes(jsonResponse.top);
})
.catch(error => {
console.log(error);
});
}, []);
useEffect(() => {
const callFilterByType = result => {
if (filterByType === "") {
callFilterByYear(result);
console.log(result);
} else {
result = result.filter(anime => anime.type === filterByType);
callFilterByYear(result);
console.log(result);
}
};
const callFilterByYear = result => {
if (filterByYear === "") {
setSortedAnimes(result);
} else {
const regex = new RegExp(`${filterByYear}`, "gi");
result = result.filter(anime => regex.test(anime.start_date));
setSortedAnimes(result);
console.log(result);
}
};
if (searchValue === "") {
callFilterByType(animes);
} else {
const regex = new RegExp(`${searchValue}`, "gi");
console.log("search : ", searchValue);
const result = animes.filter(anime => regex.test(anime.title));
callFilterByType(result);
console.log(result);
}
}, [searchValue, animes, filterByType, filterByYear]);
return (
<div className="App">
<Search
searchValue={searchValue}
setSearchValue={setSearchValue}
filterByType={filterByType}
setFilterByType={setFilterByType}
filterByYear={filterByYear}
setFilterByYear={setFilterByYear}
/>
{sortedAnimes.length > 0 ? (
sortedAnimes.map((anime, index) => {
return <Anime key={index} anime={anime} />;
})
) : (
<span>Aucune correspondance</span>
)}
</div>
);
};
export default App;
SandBox Sample
You can do first round of simplification like this:
useEffect(() => {
let result = [...animes];
if(searchValue) {
const searchRegex = new RegExp(`${searchValue}`, "gi");
result = result.filter(anime => searchRegex.test(anime.title));
}
if(filterByType) {
result = result.filter(anime => anime.type === filterByType);
}
if(filterByYear) {
const yearRegex = new RegExp(`${filterByYear}`, "gi");
result = result.filter(anime => yearRegex.test(anime.start_date));
}
setSortedAnimes(result);
}, [searchValue, animes, filterByType, filterByYear]);
It can be reduced to more compact form, like:
useEffect(() => {
const searchRegex = searchValue && new RegExp(`${searchValue}`, "gi");
const yearRegex = filterByYear && new RegExp(`${filterByYear}`, "gi");
const result = animes.filter(anime =>
(!searchRegex || searchRegex.test(anime.title)) &&
(!filterByType || anime.type === filterByType)) &&
(!yearRegex || yearRegex.test(anime.start_date))
)
setSortedAnimes(result);
}, [searchValue, animes, filterByType, filterByYear]);
More idiomatic way would be use use momoisation hook. i.e. Remove sortedAnimes as state and
const sortedAnimes = useMemo(() => {
const searchRegex = searchValue && new RegExp(`${searchValue}`, "gi");
const yearRegex = filterByYear && new RegExp(`${filterByYear}`, "gi");
return animes.filter(anime =>
(!searchRegex || searchRegex.test(anime.title)) &&
(!filterByType || anime.type === filterByType)) &&
(!yearRegex || yearRegex.test(anime.start_date))
)
}, [searchValue, animes, filterByType, filterByYear]);
try this
if you are using filter method inside jsx then you try this method.
Let me brief it,
consider userInfo like an object containing fields like name, email, location etc. so, if you want your filter method to provide your search results based on these fields value then you can use something like this in jsx.
{userInfo.filter((user) => (
user.name.toLowerCase().includes(cloneSearchTerm)
||
user.email.toLowerCase().includes(cloneSearchTerm)
||
user.location.toLowerCase().includes(cloneSearchTerm)
)
).map((user, idx) => (
<div key={idx}>
<span>{user.name}</span>
<span>{user.email}</span>
<span>{user.location}</span>
</div>
))}