Getting a map undefined error for search engine results page - javascript

I keep getting this error when it kicks over to my Suggestion method.
Uncaught TypeError: Cannot read property 'map' of undefined
in Suggestions line 4
Can anyone help me figure out why, please?
I am a newb so forgive my ignorance.
import React from 'react';
const Suggestions = props => {
const options = props.results.map (r => (
<li key={r.id}>
{r.name}
</li>
));
return <ul className="ul-list">{options}</ul>;
};
export default Suggestions;
Here is the rest of the code for the search bar. I removed api info but it was in there.
import React, {Component} from 'react';
import axios from 'axios';
import Suggestions from '../SearchBbg/suggestions';
import '../../style/searchbar.scss';
const API_KEY = '';
const API_URL = '';
class Search extends Component {
state = {
query: '',
results: [],
};
getInfo = () => {
axios
.get (`${API_URL}?api_key=${API_KEY}&prefix=${this.state.query}&limit=7`)
.then (({data}) => {
this.setState ({
results: data.data,
});
});
};
handleInputChange = () => {
this.setState (
{
query: this.search.value,
},
() => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query.length % 2 === 0) {
this.getInfo ();
}
} else if (!this.state.query) {
}
}
);
};
render () {
return (
<form>
<input
className="search-bar"
placeholder="Search for..."
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
<Suggestions results={this.state.results} />
</form>
);
}
}
export default Search;

I believe you already destructred in your axios call data property. That might be the reason why you got undefined for props.results.
So try the following:
getInfo = () => {
axios
.get(`${API_URL}?api_key=${API_KEY}&prefix=${this.state.query}&limit=7`)
.then(({data}) => { // here already destructured
this.setState ({
results: data // removed => .data,
});
});
};
Additionally also worth to check for null or undefined value like props.results && props.results.map() before using .map(), so you can use like just to be sure:
const Suggestions = props => {
const options = props.results && props.results.map (r => (
<li key={r.id}>
{r.name}
</li>
));
return <ul className="ul-list">{options}</ul>;
};
I hope this clarifies!

Your results array is undefined.
Check if you are passing results as a prop to Suggestions and if results array is not undefined.
Edit:
Seems that you are assigning some null or undefined to results, the only way this happens is on your api response.
Check if you are parsing server response correctly.

Related

The array doesnt print its values in React

I am trying to make a socket io simple chat app and the connection works fine. The problems comes with the const renderChat. The console.log is read and correctly printed (with the value in the textbox) also if I put a static text it is also displayed on the frontend, however, for some reason it doesn't print
{msg.data["message"]}
which has to print each value in an array. Also I am almost certain that the array is emptied every time and the useEffect doesn't work properly but I can't really test that yet. Would be glad if I get solution to both problems!
import './App.css';
import io from 'socket.io-client'
import { useEffect, useState } from 'react'
import React from 'react';
import ReactDOM from "react-dom/client";
const socket = io.connect("http://localhost:3001");
function App() {
const [message, setMessage] = useState("");
const [chat, setChat] = useState([]);
const sendMessage = () => {
socket.emit("send_message", { message });
};
const renderChat = () => {
return (
chat.forEach(msg => {
<h3>{msg.data["message"]}</h3>
console.log(msg.data)
})
)
}
useEffect(() => {
socket.on("receive_message", message => {
setChat([...chat, message]);
});
}, [socket])
return (
<div className="App">
<input placeholder="Message..." onChange={(event) => {
setMessage(event.target.value);}}
/>
<button onClick={sendMessage}>Send Message</button>
<h1>Message:</h1>
{renderChat()}
</div>
);
}
export default App;
EDIT:
Thanks to Ibrahim Abdallah, now the printing works. The only thing that doesn't work is as I feared, storing the information. Here is the piece of code
useEffect(() => {
socket.on("receive_message", message => {
setChat([...chat, message]);
});
}, [socket])
For some reason when I enter for example "text1" it saves it but then if I enter "text2" it overwrites the first value "text1".
modify your renderChat function to use map instead and the console log message should be before the return statement of the map function
const renderChat = () => {
return (
chat.map(msg => {
console.log(msg.data)
return (
<h3>{msg.data["message"]}</h3>
)
})
)
}
Please use map instead of forEach
const renderChat = () => {
return (
chat.map(msg => {
<h3>{msg.data["message"]}</h3>
console.log(msg.data)
})
)
}
Use map instead of forEach
map actually returns an array of the returned results.
forEach on the other hand does not return anything.
const renderChat = () => {
return (
chat.map(msg => {
console.log(msg.data);
return <h3>{msg.data["message"]}</h3>;
})
)
}

React: How to avoid duplication in a state array

I am making MERN social media app.
I want to show all the friends of the current user in a list in SideBar.jsx .
Home.jsx (parent of Sidebar.jsx)
import React, { Component } from "react";
import { Person } from "#material-ui/icons";
import Topbar from "../../components/topbar/Topbar";
import Sidebar from "../../components/sidebar/Sidebar";
import Feed from "../../components/feed/Feed";
import Rightbar from "../../components/rightbar/Rightbar";
import "./home.css";
export default function Home() {
return (
<>
<Topbar />
<div className="homeContainer">
<Sidebar />
<Feed />
<Rightbar />
</div>
</>
);
}
SideBar.jsx
import "./sidebar.css";
import React, { Component, useContext, useState } from "react";
...
import { axiosInstance } from "../../config";
export default function Sidebar() {
const { user } = useContext(AuthContext);
const [followings, setFollowings] = useState([]);
const followingsList = user.followings;
useEffect(() => {
const fetchFollowings = async () => {
followingsList.map(async (id) => {
try {
const theUser = await axiosInstance.get(`/users?userId=${id}`);
if (followings.includes(theUser.data)) {
} else {
setFollowings((prev) => [...prev, theUser.data]);
}
} catch (error) {
console.log(error);
}
});
};
fetchFollowings();
}, [user]);
return (
<div className="sidebar">
.....
<ul className="sidebarFriendList">
{followings.map((u) => (
<CloseFriend key={u._id} user={u} />
))}
</ul>
</div>
</div>
);
}
For example, in this case, in the state "followings", there are 2 user objects.
So, the line
followings.map((u) => (...
should only show 2 entries.
However, the result is below.
As you can see, it is showing each friend twice.
I tired to check if a user object already exists in followings by doing
if (followings.includes(theUser.data)) {
} else {
setFollowings((prev) => [...prev, theUser.data]);
}
But this is not working.
How can I make sure that it only shows each user once?
I want it to be like this
Any help would be greatly appreciated. thank you
This is happening because it seems that your useEffect method is being fired two times (probably because you are using React.StrictMode) and you are setting the state inside the .map method (that is not good because you trigger a new render each time you call the setState).
What I would recommend you to do, is to remove the setState from the .map method and just set the new state after you format your data. So it would be something like this:
const newFollowings = followingsList.map(async (id) => {
try {
const theUser = await axiosInstance.get(`/users?userId=${id}`);
return theUser.data;
} catch (error) {
console.log(error);
}
});
setFollowings(newFollowings);
Probably you would have to add a filtering to the array in case there are some errors (because on errors the mapped value would be undefined):
.filter(data => data);
When you are using the .map function with async/await Promise.all usually always does the trick. Instead of pushing the state on every iteration you collect the followers list and set the state when all your fetching is done. I did not test it yet, but I hope it works.
const followingsList = user.followings;
useEffect(() => {
const fetchFollowings = async () => {
const list = await Promise.all(followingsList.map(async (id) => (
await axios.get('/user?userId=' + id);
)));
setFollowers(list);
};
fetchFollowings();
}, [user]);
Note: let me know if it works, if not I'll do a little sandbox on my own

How to await data coming from an API as a prop in a component?

I am trying to send a prop on a component once my data loads from an API. However, even though I am using async await to await from my data, I am still getting an error of: Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
My process:
Have two set states: 1) updateLoading and 2) setRecordGames
Once the data is loaded, I will set updateLoading to false and then set recordGames with the data from the API.
My Problem:
It seems that when I pass data in the component, React does not wait for my data load and gives me an error.
This is my code:
import useLoadRecords from '../../hooks/useLoadRecords'
import { CustomPieChart } from '../charts/CustomPieChart'
export const GameDistribution = () => {
const { records, loading } = useLoadRecords()
let data = records
console.log(data) // This logs out the records array
return (
<div>
// once loading is false, render these components
{!loading ? (
<>
<div>
{recordGames.length > 0 ? (
// This line seem to run as soon as the page loads and I think this is the issue. recordGames is empty at first, but will populate itself when the data loads
records.length > 0 && <CustomPieChart data={data} />
) : (
<div>
No games
</div>
)}
</div>
</>
) : (
<span>Loading...</span>
)}
</div>
)
}
// useLoadRecords.js
import { useState, useEffect } from 'react'
import { API } from 'aws-amplify'
import { listRecordGames } from '../graphql/queries'
// Centralizes modal control
const useLoadRecords = () => {
const [loading, setLoading] = useState(false)
const [records, updateRecords] = useState([])
useEffect(() => {
fetchGames()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const fetchGames = async () => {
try {
let recordData = await API.graphql({
query: listRecordGames,
})
setLoading(false)
let records = recordData.data.listRecordGames.items.map(
(item) => item.name
)
let result = Object.values(
records.reduce((acc, el) => {
if (!acc[el]) acc[el] = { name: el, plays: 0 }
acc[el].plays++
return acc
}, {})
)
updateRecords(result)
} catch (err) {
console.error(err)
}
}
return { records, loading }
}
export default useLoadRecords
I would make a hook for the data and setData to it when fetching ,
you can clone the data using spread operator and this pass it.
or better return that component only if there is something in data for example
{
data && <CustomPieChart data={recordGames} />
}
and it would be nice to make a some loader (gif/svg) using your loading hook so it can be shown when the data is still being fetched.

Objects are not valid as react child, but I'm using React docs official code [duplicate]

I am trying to render a list of posts by mapping through an array. I've done this many times before but for some reason
renderPosts = async () => {
try {
let res = await axios.get('/posts');
let posts = res.data;
return posts.map((post, i) => {
return (
<li key={i} className="list-group-item">{post.text}</li>
);
});
} catch (err) {
console.log(err);
}
}
render () {
return (
<div>
<ul className="list-group list-group-flush">
{this.renderPosts()}
</ul>
</div>
);
}
All I get is:
Uncaught Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
I've checked the data returned from renderPosts and it is an array with the correct values and no promises. What's going on here?
I also received the same error message when creating an async functional component. Functional components should not be async.
const HelloApp = async (props) => { //<<== removing async here fixed the issue
return (
<div>
<h2>Hello World</h2>
</div>
)
}
ReactDOM.render(<HelloApp />, document.querySelector("#app"))
jsfiddle
this.renderPosts() will return a Promise not the actual data, and AFAIK Reactjs will not resolve Promises implicitly in render.
You need to do it like this
componentDidMount() {
this.renderPosts();
}
renderPosts = async() => {
try {
const res = await axios.get('/posts');
const posts = res.data;
// this will re render the view with new data
this.setState({
Posts: posts
});
} catch (err) {
console.log(err);
}
}
render() {
const posts = this.state.Posts?.map((post, i) => (
<li key={i} className="list-group-item">{post.text}</li>
));
return (
<div>
<ul className="list-group list-group-flush">
{posts}
</ul>
</div>
);
}
Using React Hooks:
UPDATE 2020-08-01: Amended with #ajrussellaudio's suggestion.
import React, {useState, useEffect} from "react"
const ShowPosts = () => {
const [posts, setPosts] = useState([]);
useEffect( () => {
async function fetchData() {
try {
const res = await axios.get('/posts');
setPosts(res.data);
} catch (err) {
console.log(err);
}
}
fetchData();
}, []);
return <div>{posts}</div>
}
Poor me
For anyone using jest test
And trying to call a function children then received this Error
please check:
const children = jest.fn().mockReturnValueOnce(null)
NOT
const children = jest.fn().mockRejectedValue(null);
If you use Next.js you can use this:
Put your codes in {}, if you have some connection or some calculations thats need time, add await to your codes in {}.
import { useEffect } from 'react'
useEffect(async () => {.......},[])

TypeError: Cannot read property 'name' of undefined - Fetching data from restcountries API

I am building an app following the Rest Countries API challenge from frontendmentor. I have run into a problem. When trying to find the border countries full name using the alpha3code, I get the error :
TypeError: Cannot read property 'name' of undefined.
import React, { useState, useEffect } from "react";
import axios from "axios";
import {Link} from "react-router-dom";
import {CountryDetailStyles, ImgContainer, CountryInfo, CountryInfoDetails, CountryDetailsWrapper, CountryInfoDetailsInner, CountryBorders, LinkWrapper} from "../styles/CountryDetailStyles";
function CountryDetail({match}) {
useEffect(() => {
fetchItem();
}, []);
useEffect(() => {
setBorderCountries();
}, []);
const [item, setItem] = useState([]);
const [allCountries, setAllCountries] = useState([]);
const fetchItem = async () => {
const response = await axios.get(`https://restcountries.eu/rest/v2/name/${match.params.name}?fullText=true`);
setItem(response.data)
}
const setBorderCountries = async () => {
const response = await axios.get(`https://restcountries.eu/rest/v2/all`);
setAllCountries(response.data)
}
// get borders full name using alpha3code
const getBorderCountryName = (allCountries, border) => {
const matchingCountry = allCountries.find(country => {
return country.alpha3Code === border;
})
return matchingCountry.name;
}
return (
<CountryDetailStyles>
<Link className="country-links" to={"/"}>
<LinkWrapper>
<p><i className="fas fa-arrow-left"></i>Go Back</p>
</LinkWrapper>
</Link>
{item.map(i => (
<CountryDetailsWrapper>
<ImgContainer flag={i.flag}>
</ImgContainer>
<CountryInfo>
<h1>{i.name}</h1>
<CountryInfoDetails>
<CountryInfoDetailsInner>
<p><span>Native Name: </span>{i.nativeName}</p>
<p><span>Population: </span>{i.population.toLocaleString()}</p>
<p><span>Region: </span>{i.region}</p>
<p><span>Sub Region: </span>{i.subregion}</p>
<p><span>Capital: </span>{i.capital}</p>
</CountryInfoDetailsInner>
<CountryInfoDetailsInner className="second">
<p><span>Top Level Domain: </span>{i.topLevelDomain}</p>
<p><span>Currencies: </span>{i.currencies[0].name}</p>
<p><span>Languages: </span>{i.languages.map(lang => lang.name).join(", ")}</p>
</CountryInfoDetailsInner>
</CountryInfoDetails>
<CountryBorders>
<p><span>Border Countries:</span></p>
{i.borders.map(border =>{
const borderName = getBorderCountryName(allCountries, border);
return (<Link to={`/country/${borderName}`}><button>{borderName}</button></Link>)
})}
</CountryBorders>
</CountryInfo>
</CountryDetailsWrapper>
))}
</CountryDetailStyles>
)
}
export default CountryDetail;
The issue is here:
{item.map(i => (
you are getting this response from an axios call and setting in state which is this:
const response = await axios.get(`https://restcountries.eu/rest/v2/name/${match.params.name}?fullText=true`);
setItem(response.data)
on first render item doesn't have any data as axios call will take some time to complete and when there is no data an you try to iterate it then it will throw such error. So try this:
{
( item && item.length > 0 ) // conditional rendering or such type of check is neede before iteration
?
// Iterate here
:
null // do nothing
}

Categories

Resources