Error in Map function: Cannot read property of 'map' undefined
While using the map function, I went through each documentation but and also copied the same exact code whjich was actual running on the github but was unable to run in the local server. It's always saying the same and same problem everytime as the map function is underdefined.
Please get the following code which I used in my project.
import React, { useState, useEffect } from "react";
import axios from "./axios";
import "./Row.css";
import Youtube from "react-youtube";
const baseImgUrl = "https://image.tmdb.org/t/p/original";
function Row({ title, fetchUrl, isLargeRow }) {
const [movies, setMovies] = useState([]);
const [trailerUrl, setTrailerUrl] = useState("");
// Options for react-youtube
const opts = {
height: "390",
width: "100%",
playerVars: {
autoplay: 1,
},
};
useEffect(() => {
async function fetchData() {
const request = await axios.get(fetchUrl);
setMovies(request.data.results);
return request;
}
fetchData();
}, [fetchUrl]);
const handleClick = async (movie) => {
if (trailerUrl) {
setTrailerUrl("");
} else {
let trailerurl = await axios.get(
`/movie/${movie.id}/videos?api_key=somekeyhere`
);
setTrailerUrl(trailerurl.data.results[0]?.key);
}
};
return (
<div className="row">
<h2>{title}</h2>
<div className="row_posters">
{movies.map(
(movie) =>
movie.backdrop_path !== null && (
<img
className={`row_poster ${isLargeRow && "row_posterLarge"}`}
src={`${baseImgUrl}${
isLargeRow ? movie.poster_path : movie.backdrop_path
}`}
alt={movie.name}
key={movie.id}
onClick={() => handleClick(movie)}
/>
)
)}
</div>
{trailerUrl && <Youtube videoId={trailerUrl} opts={opts} />}
</div>
);
}
export default Row
Eventhough you set [] for movies as initial state using useState it is reassigned by the response of your request.
Make sure request.data.results is not undefined
Related
I have this code that controls the behavior of what to map from an array onClick. The useState is set to a string const [activeFilter, setActiveFilter] = useState('All'); that is supposed to automatically filter all products containing the string as tag but it doesn't do this automatically and I can't figure out why. Please help with code below.
index.js
import React, { useEffect, useState } from 'react'
import {client} from '../lib/client'
import { Product, FooterBanner, HeroBanner } from '../components'
const Home = ({products, bannerData}) => {
const [productItems, setProductItems] = useState([])
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
useEffect(() => {
setProductItems(products)
}, [])
const handleProductFilter = (item) => {
setActiveFilter(item)
setTimeout(() => {
if (item == 'All'){
setFilterWork(productItems)
}else{
setFilterWork(productItems.filter((productItem)=> productItem.tags.includes(item)))
}
}, 500)
}
return (
<>
<HeroBanner heroBanner={bannerData.length && bannerData[0]} />
<div className='products-heading'>
<h2>Best Selling Products</h2>
<p>Smoke accessories of many variations</p>
</div>
<div className='product_filter'>
{['Lighter', 'Pipe', 'Roller', 'Hookah', 'All'].map((item, index) => (
<div
key={index}
className={`product_filter-item app__flex p-text ${activeFilter === item ? 'item-active' : ''}`}
onClick={() => handleProductFilter(item)}
>
{item}
</div>
))}
</div>
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
<FooterBanner footerBanner={bannerData && bannerData[0]} />
</>
)
};
export const getServerSideProps = async () => {
const query = '*[_type == "product"]'
const products = await client.fetch(query)
const bannerQuery = '*[_type == "banner"]'
const bannerData = await client.fetch(bannerQuery)
return {
props: {products, bannerData}
}
}
export default Home
The image below is what it looks like on load and the only time All products containing 'All' tags are visible is when the All button is clicked on again, regardless of it being active initially
No products are being displayed initially when the component renders because the displayed products are loaded from the filterWork state that is only set once an onClick event is triggered. To fix this you can simply set the initial products in the useEffect because you are starting with all the products being displayed.
useEffect(() => {
setProductItems(products);
setFilterWork(products);
}, [])
I have this component in my React project -
const ViewPost = (props: Props) => {
const [listingData, setListingData] = useState<any>({})
const [auctionData, setAuctionData] = useState<any>({})
useEffect(() => {
if (props.listingId) {
getListingData()
}
}, [props.listingId])
const getListingData = async () => {
const { data } = await getListingById(props.listingId)
setListingData(data?.data)
if (data.data.isTimedAuction) {
auctions(data.data.auctionId)
}
}
const auctions = async (auctionId: any) => {
const auction = await getAuctions(auctionId)
console.log('auction', auction.data)
setAuctionData(auction.data)
}
return (
<>
<Navbar />
<div className={classes.viewPostPage}>
<div className={classes.bodyContainer}>
<Details
data={listingData as any}
updateListing={getListingData}
auctionData={auctionData}
/>
</div>
</div>
</>
)
}
export default ViewPost
Basically, I'm getting data from an API and assigning it to auctionData.
console.log(auction.data) shows me the desired result but when I pass auctionData as props into Details I get an empty object which leads to a lot of issues, since useState is async.
How can I overcome this problem?
const [auctionData, setAuctionData] = useState<any>({})
your default value is an empty object, that causes the problems.
should set null or undefined as default value, and hide the Details when not have the data.
Use loading state. Once data is fully fetched from api then pass to child component. I think what is happeing here is that child component is called with empty state variable while data is still being fetched.
const [isLoading, setIsLoading] = useState(true)
const getListingData = async () => {
const { data } = await getListingById(props.listingId)
.then((data) => {setListingData(data)})
.then((data) => {
setTimeout(() => {
setIsLoading(false)
}, 1000)
})
if (data.data.isTimedAuction) {
auctions(data.data.auctionId)
}
}
and then return
if (isLoading) {
return (
<div>
Loading...
</div>
)
}
return (
<>
<Navbar />
<div className={classes.viewPostPage}>
<div className={classes.bodyContainer}>
<Details
data={listingData as any}
updateListing={getListingData}
auctionData={auctionData}
/>
</div>
</div>
</>
)
}
I have used Redux for state management in my application - with React Hooks. I am able to hit the API and get the response back like below screenshots from my action creator logs:
Here's the code of my component where I'm trying to display the results:
import { useState, useEffect } from "react";
import {Link} from 'react-router-dom';
import styled from "styled-components";
import {GoSearch} from 'react-icons/go';
import LoaderSpinner from "../components/LoaderSpinner";
import MovieItem from "../components/MovieItem";
import { RootStateOrAny, useDispatch, useSelector } from "react-redux";
import {fetchAllShows} from '../actions/movies';
import {searchMovieByTitle} from '../actions/search';
const Home = () => {
const [loading, setLoading] = useState(true);
const [searchString, setSearchString] = useState('');
const [isFromSearchResults, setIsFromSearchResults] = useState(false);
const dispatch = useDispatch();
const movies = useSelector((state: RootStateOrAny) => state.shows)
const searchResults = useSelector((state: RootStateOrAny) => state.shows);
useEffect(()=> {
setLoading(true);
dispatch(fetchAllShows());
setIsFromSearchResults(false);
}, [dispatch])
const handleSearchChange = (e: any) => {
e.preventDefault();
setSearchString(e.target.value);
}
const findMovieByTitle = () => {
dispatch(searchMovieByTitle(searchString));
setIsFromSearchResults(true);
setSearchString('');
}
console.log(isFromSearchResults);
var start, max, paginatedArr=[], pageSize = 25;
for(start = 0; max = movies.length, start < max; start += pageSize) {
paginatedArr = movies.slice(start, start + pageSize);
}
return <HomeContainer>
<div className="search-bar">
<input
type="text"
placeholder="Search for a movie"
value={searchString}
onChange={handleSearchChange}
/>
<div className="search" onClick={findMovieByTitle}>
<GoSearch />
</div>
</div>
<div className="grid">
{
isFromSearchResults
? <div>
{
searchResults.map((result: any, index: number) => {
console.log(result);
// console.log(result.show);
return <Link
key={index}
to={{pathname:`/movies/${result.show.id}`,
state: {movie: result.show}}}
>
<MovieItem show={result.show} />
</Link>
})
// errors out in this return statement. It says the result (from array mapped out above is null) whereas the action creator is able to print out the full search queries
}
</div>
: movies.length == 0
? <div className="loader">
<LoaderSpinner
isLoading={loading}
loadingText="Fetching Movies..."
/>
</div>
// : movies.map((movie:any, index:number) => {
: paginatedArr.map((movie:any, index:number) => {
return <Link
to={{pathname:`/movies/${movie.id}`,
state: {movie: movie}}} key={index}
>
<MovieItem show={movie} />
</Link>
})
}
</div>
</HomeContainer>
}
export default Home;
Here's the code for my action creator to make the search API call:
import {
SEARCH_MOVIE_BY_TITLE,
} from './types';
import ShowsService from '../services/ShowsService';
export const searchMovieByTitle = (title: string) => async (dispatch: any) => {
try {
let response = await ShowsService.searchMovieByTitle(title);
console.log(typeof(response.data));
// console.log(response.data);
const promise = response.data.map((items: any) => {
// console.log(items);
return items;
})
const searchArr = await Promise.all(promise);
console.log(searchArr);
dispatch({type: SEARCH_MOVIE_BY_TITLE, payload: searchArr});
} catch (err) {
console.log(err);
}
}
The problem now lies in trying to parse the search results array and display it as a list of <MyComponent /> with movie passed as props. It shows undefined for each of the items passed. How do I resolve this?
Sample error log attached below:
I looked into your code and it seems your logs were only in the searchMovieByTitle action. It seems there are no checks for the availability of data in the rendered view. Usually when you perform fetching actions, you also check if the data has been fetched successfully before starting to use it.
In your code, this could be done like this:
<div className="grid">
{isFromSearchResults && searchResults?.length ? ( // added check for searchResult here, to make sure it has data
<div>
{
searchResults.map((result: any, index: number) => {
console.log(result);
console.log(result.show?.id); // it is suggested to also check if the id exists in all the result.show objects
return (
<Link
key={index}
to={{
pathname: `/movies/${result.show?.id}`, // optionally add the "?." before id in order to avoid crushes if the id doesn't exist, though this depends on your backend logic; if they must always have an id, no need to add the "?."
state: { movie: result.show },
}}
>
<MovieItem show={result.show} />
</Link>
);
})
// errors out in this return statement. It says the result (from array mapped out above is null) whereas the action creator is able to print out the full search queries
}
</div>
) : movies.length == 0 ? (
<div className="loader">
<LoaderSpinner isLoading={loading} loadingText="Fetching Movies..." />
</div>
) : (
// : movies.map((movie:any, index:number) => {
paginatedArr.map((movie: any, index: number) => {
return (
<Link
to={{ pathname: `/movies/${movie.id}`, state: { movie: movie } }}
key={index}
>
<MovieItem show={movie} />
</Link>
);
})
)}
</div>
If you look closely, I've added a check for the length of the array, making sure the data is in there before starting to map it.
Also added a check for the id, to see if all your result.show objects have it.
On reactjs, how can I setNotify again a state that is on a component?
import React, { useState } from 'react'
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
// if (props.message === "") {
// props.message = "Some Error"
// }
// if (props.message !== "") {
// setNotify(false)
// }
// if (props) {
// const [notify] = useState(true)
// }
console.log("notify.state:", props)
const closeNotification = (e) => {
console.log("Should be closing notification")
setNotify(e)
}
return (
<div className="notification is-danger" style={notify ? {display: 'none'} : {display: 'block'}}>
<button className="delete" onClick={() => closeNotification(true)}></button>
Error: {props.message}
</div>
)
}
export default NotificationError
If I use the following:
if (props) {
const [notify] = useState(true)
}
I get the error,
Line 17:26: React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
If I use the following
if (props.message !== "") {
setNotify(true)
}
It throws the following...
Error: Too many re-renders. React limits the number of renders to
prevent an infinite loop.
Simply, I am not understanding this. Can you please help? :(
Rewrite you logic to something like:
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
useEffect(() => {
if (props.message === "") {
props.setMessage('Some Error');
}
setNotify(false);
}, [props.message]);
return (
<div
className="notification is-danger"
style={notify ? { display: "none" } : { display: "block" }}
>
<button className="delete" onClick={() => setNotify(true)}></button>
Error: {props.message}
</div>
);
};
Props are immutable so if you want to change a message you should pass a callback.
Also, take a read about Rules of Hooks.
Use a useEffect hook for such cases. It works similar to componentDidMount and componentDidUpdate in class component. It means the function that you pass as the first argument of useEffect hook triggers the first time when your component mounts and then every time any of the elements of the array changes that you pass as the second argument.
Here is the code example:
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
useEffect(() => {
if(props.message != '') {
setNotify(false);
}
}, [props.message])
const closeNotification = (e) => {
console.log("Should be closing notification")
setNotify(e)
}
return (
<div className="notification is-danger" style={notify ? {display: 'none'} : {display: 'block'}}>
<button className="delete" onClick={() => closeNotification(true)}></button>
Error: {props.message}
</div>
)
}
Situation:
first, I fetch imgs list from database:
{imgs:
[
{_id: '...',img:'***.png'},
...,
]
}
then, signature img.src using ali-oss-hook, results like:
{imgs:
[
{_id:'...', img: '***.png', src: 'signatured-http-address'}
...,
]
}
then, pass the imgs to PictureList component :
<PictureList imgs={images}
PictureList receive the new props,but didn't render it
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
Code
Picture.js
import React, {useEffect, useState, useRef } from 'react'
import { useAlioss } from '../../hooks/oss-hook'
import PictureList from '../../components/PictureList'
import './style.less'
const Pictures = () => {
const [loading, setLoading] = useState(true)
const [signatured, setSignatured] = useState(false)
const [results, setResults] = useState()
const [images, setImages] = useState([])
const { allowUrl } = useAlioss()
const resultsDoSetRef = useRef(false)
async function getImages() {
try {
const dbResponse = await fetch(
`${process.env.REACT_APP_BACKEND_URL}/upload/images`
);
const resu = await dbResponse.json();
setResults(resu)
resultsDoSetRef.current = true
} catch (e) {
console.log("get images failed")
} finally {
setLoading(false)
console.log("get images done!")
}
}
useEffect(() => {
getImages();
}, [])
async function signatureUrl(raw) {
setSignatured(false)
try {
let tempImgs = []
raw.imgs.forEach((r) => {
allowUrl(r.img).then((res) => {
r.img = res;
tempImgs.push(r)
});
});
setImages(tempImgs);
} catch (e) {
console.log("signature failed",e)
} finally {
setSignatured(true)
console.log("signature done!")
}
}
useEffect(() => {
if (resultsDoSetRef.current) {
resultsDoSetRef.current = false
signatureUrl(results);
}
},[results])
return (
<div className="picture">
{loading ? <h1>Loading</h1> : <PictureList imgs={images} />}
</div>
);
};
export default Pictures
PictureList.js
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
chrome react devTool component shows props
chrome devTool element shows empty PictureList
In chrome devTool react component shows right props, but the PictureList component still empty <ul></ul>.
Which part is wrong?
Look at the PictureList.js, you are receving "imgs" as the arugument of the function, this is not the same as property you passed in
<PictureList imgs={images}
This "imgs" is actually an object that has a property imgs, so your code will become:
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
P.S : Just a suggestion, generally props(or something similarly descriptiove) is used as the parameter argument, so your code will be something like this:
const PictureList = ({ props }) => {
return (
<ul>
{props.imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
In Picture --signatureUrl() method, raw.imgs.forEach() returns a bounch of promises, these promises can't resolve all at once.
When setImages(tempImgs) ,the images in useState hook receive a empty array first, then push new image to images array when raw.imgs.forEach() returned promise resolve a new image item.
So, we must wait all allowUrl(r.img) promises resolved, then setImages(tempImgs).
function signatureUrl(raw) {
const tasks = raw.imgs.map(i => allowUrl(i.img))
Promise.all(tasks).then(values => {
let resultImgs = raw.imgs.map((t, index) => ({ ...t, src: values[index] }));
setImages(resultImgs)
})
}
PS: Solution do works, but all analizes may be wrong, for reference only.