I'm currently trying to create a weather website using weatherapi, but I'm running into a problem. If I log the object of location there's no error, but if I try to log anything deeper than that object, like the name of the city it cannot read properties of undefined. If I comment out the log when it's using the name of the city, then uncomment it again and don't reload the page, then it will log the name of the city without error.
import React from 'react';
import './index.css';
import Navbar from "./components/Navbar"
import {useState} from "react"
import Weather from './components/Weather';
function App() {
const [inputData, setInputData] = useState({})
const [currentWeather, setCurrentWeather] = useState([])
const [loc, setLoc] = useState({loc:"Arlington"})
let apiKey = "xxxxxxxx"
// console.log("Location: "+ loc.loc)
React.useEffect(() =>{///Finds weather data of certain location
console.log(loc.loc)
fetch(`https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${loc.loc}&aqi=no`)
.then(res => {
if(res.ok){
return res.json()
}
})
.then(data => {
if(data !=null){//Only change currentWeather when there is data for it
setCurrentWeather(data)
}else{
alert(`${loc.loc} was not found`)
}
})
}, [loc])
React.useEffect(() =>{///Finds locations with search bar
fetch(`https://api.weatherapi.com/v1/search.json?key=${apiKey}&q=${loc.loc}&aqi=no`)
.then(res => res.json())
.then(data => {
if(data.loc == null){
}else{
setLoc(data)
}
})
}, [])
//console.log(currentWeather.location.name)
return (
<div className="App">
<Navbar inputData={inputData} setLoc={setLoc} setInputData={setInputData}/>
<Weather currentWeather={currentWeather}/>
</div>
);
}
export default App;
A few issues I see:
You are initializing currentWeather to an array, but it should probably be either undefined or an object ({}) based on your use of it. To fix this, use one of these:
const [currentWeather, setCurrentWeather] = useState()
// or
const [currentWeather, setCurrentWeather] = useState({})
You are trying to access currentWeather.location.name before you have updated currentWeather to have those properties. That's why you're getting that error.
The best solution for this is to use optional chaining https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
So try this:
console.log(currentWeather?.location?.name)
Your console.log is just in the body of the function component, meaning it will only be called once (with the initial value), I think. To fix this, and log every time the value of currentWeather changes, you can do this instead:
React.useEffect(() => { console.log(currentWeather?.location?.name) }, [currentWeather])
Related
So im making a function in react that enables me to connect my react page with my metamask and display my nfts that ive purchaed on opensea onto my webpage once logged in but im facing an error of
Cannot read properties of undefined (reading 'map') at NFTContainer
The error is occurring in NftContainer its saying undefined reading of map but I'm sure I've defined it if you know where I've gone wrong in this please help and drop a solution down below I was expecting to see the names of Nfts I have in my metamask to show nothing but the error is now appearing
import { cleanup } from '#testing-library/react';
import { NoEthereumProviderError } from '#web3-react/injected-connector';
import { useEffect, useState } from 'react';
import './nft.css'
import NFTContainer from './NFTContainer'
export function Nft() {
const [walletAddress, setWalletAddress] = useState(null)
const [nfts, setNfts] = useState()
const connectWallet = async () => {
if (typeof window.ethereum !== 'undefined') {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0])
}
}
const getNftData = async () => {
if (!walletAddress) return;
const response = await fetch(`https://api.rarible.org/v0.1/items/byOwner/?owner=ETHEREUM:${walletAddress}`)
const data = await response.json()
debugger
}
useEffect(() => {
getNftData()
}, [walletAddress])
return (
<div className='Nft'>
<div className='text'>
Account: {walletAddress}
</div>
<button className='connect-button' onClick={connectWallet}>
Connect Wallet
</button>
<NFTContainer nfts={nfts} />
</div>
);
}
export default Nft;
import React from 'react'
import NFTCard from './NFTCard'
const NFTContainer = ({ nfts }) => {
let nftToRender;
return (
<div>
{nftToRender = nfts.map((nft, index) => {
return <NFTCard nft={nft} key={index} />
})}
</div>
)
}
export default NFTContainer
import React from 'react'
const nftCard = ({ nft }) => {
return (
<div>
{nft.meta.name}
</div>
)
}
export default nftCard
Because the value is undefined. This is the only place you use .map():
{nftToRender = nfts.map((nft, index) => {
return <NFTCard nft={nft} key={index} />
})}
And that nfts variable comes from props:
const NFTContainer = ({ nfts }) => {
Which is provided to the component:
<NFTContainer nfts={nfts} />
Which is defined in state:
const [nfts, setNfts] = useState()
And since it's never given a value, it's undefined.
You can define it with a default value of an empty array:
const [nfts, setNfts] = useState([])
This should eliminate the error, allowing .map() to just be called on an empty array and quietly not iterate over anything.
Of course, you probably also want to get actual data for it at some point. In the same component where that state is maintained you are making an AJAX call, but never do anything with this result:
const data = await response.json()
Is data the new array you want to use? In that case you'd set it to the state:
setNfts(data);
Or if some property on data is what you want:
setNfts(data.someProperty);
Either way, in order to update the state to the new value you'll need to call setNfts at some point.
import { useSelector, useDispatch } from "react-redux";
import { useEffect, useState } from "react";
import request from "../../requests";
import { fetchMovies } from "../../feautures/movies/moviesSlice";
import "./SingleMoviePage.scss";
import Rating from "../../components/UI/Rating/Rating";
import axios from "axios";
const SingleMoviePage = ({ match }) => {
const dispatch = useDispatch();
const [movieDetails, setMovieDetails] = useState({})
/* params */
const movieId = match.params.id;
const page = match.params.page;
const genre = match.params.genre;
/* movies reducer handle */
const movies = useSelector((state) => state.movies.movies);
const moviesStatus = useSelector((state) => state.movies.status);
/* movieDetails reducer handle */
/* base urls */
const baseImgUrl = "https://image.tmdb.org/t/p/original";
const movieDetailUrl = `https://api.themoviedb.org/3/movie/${movieId}?api_key=c057c067b76238e7a64d3ba8de37076e&language=en-US`;
useEffect(() => {
const fetchData = async() => {
let response = await axios.get(movieDetailUrl);
response = response.data;
setMovieDetails(response)
}
fetchData()
},[movieDetailUrl])
console.log("data: ",movieDetails )
let content;
if (moviesStatus === "loading") {
<div>Loading ...</div>;
} else if (moviesStatus === "succeeced") {
let movie = movies.find((movie) => movie.id.toString() === movieId);
content = (
<div
className="single-movie__container"
style={{
backgroundImage: `url(${
movie.backdrop_path
? baseImgUrl + movie.backdrop_path
: baseImgUrl + movie.poster_path
})`,
}}
>
<div className="single-movie__information">
<h1 className="single-movie__title">{movie.title}</h1>
<div className="single-movie__rate">
<Rating
rating={movie.vote_average}
className="single-movie__stars"
/>
<div className="single-movie__average">
{movie.vote_average}(Imdb)
</div>
</div>
<p className="single-movie__overview">{movie.overview}</p>
<p className="single-movie__genres">
<label>Genres</label>
{
movieDetails.genres.map(genre => {
console.log("genre: ",genre)
return(
<div>{genre.name}</div>
)
})
}
</p>
</div>
</div>
);
}
useEffect(() => {
if (genre === "POPULAR") {
dispatch(fetchMovies(request.fetchPopular(page)));
} else if (genre === "NOW PLAYING") {
dispatch(fetchMovies(request.fetchNowPlaying(page)));
} else if (genre === "UP COMING") {
dispatch(fetchMovies(request.fetchUpComing(page)));
}
}, [dispatch, genre, page]);
return <div className="single-movie">{content}</div>;
};
export default SingleMoviePage;
I'm trying to make a movie website with react-redux. The issue is when I try to get movie details using useEffect and try to map that in:
<p className="single-movie__genres">
I get TypeError: Cannot read property 'map' of undefined error and I get empty object (data: {}) using console.log("data: ", movieDetails).
But if I refresh the page everything works well and I get
data:
{
adult: false,
backdrop_path: "/6MKr3KgOLmzOP6MSuZERO41Lpkt.jpg",
...
}
using console.log("data: ", movieDetails). Why can't I get data when the page is first loaded?
It is because your initial state does not contain "genres" array inside the object. And when react tries to handle
movieDetails.genres.map(...)
it fall down because movieDetails.genres is undefined (and undefined does not support map method of course). Either include empty array in you initial state like:
const [movieDetails, setMovieDetails] = useState({genres:[]})
or use "?" operator in your chain like:
movieDetails.genres?.map(...)
.map method is a prototype function for type array. you should declare moviedetails as an array like this when setting the default value using useState hook.
const [movieDetails, setMovieDetails] = useState([])
There is a point that i dont understand.As far as i know when the component is first loaded first useEffect worked and filled my movieDetails with datas after that map func worked.I mean js works top to bottom and
shouldn't movieStatus be filled with data until it comes to the map function?
Widgets.js
import React, {useContext} from 'react';
import { DataContext } from '../contexts/DataContext';
const Widgets = () => {
const {updates} = useContext(DataContext);
console.log(updates);
return (
<div className="MainWidget">
<ul>
{updates.map(update => {
return (
<div>
<li>{update.current.condition}</li>
<p>{update.current.temp_c}</p>
</div>
);
})}
</ul>
</div>
);
}
export default Widgets;
I'm mapping data from an API which is returning an error: TypeError: updates.map is not a function but its actually returning the data in the console using the console.log() function.
DataContext.js
:I'm using axios to fetch data from weatherapi.com and setting the state 'updates' with 'setUpdates' function.
import React, {useState, useEffect, createContext} from 'react';
import axios from 'axios';
export const DataContext = createContext();
const DataContextProvider = (props) => {
const [updates, setUpdates] = useState({});
const url = "https://api.weatherapi.com/v1/current.json?key=931701d0de0c4d05b0b34936203011&q=London";
useEffect(() => {
axios.get(url)
.then(res => {
console.log(res.json())
setUpdates(res.data)
})
.catch(err => {
console.log(err)
})
})
return (
<div>
<DataContext.Provider value={{updates}}>
{props.children}
</DataContext.Provider>
</div>
)
}
export default DataContextProvider;
You're requesting data for only one location and the API returns you an object, not an array. You can either change your component to expect an object, or update the context provider, so it provides an array. That will looks something like:
const [updates, setUpdates] = useState([]);//note you want an array ([]) not an object ({})
...
let data = res.data
if(!data instanceof Array) data = [data] //put an object into array if needed
setUpdates(data)
UPDATE
In you repo make following changes:
In the DataContext you need to parse JSON response into an object, so replace
axios.get(url)
.then(res => {
setUpdates(res.data.current)
})
with
axios.get(url)
.then(res => res.json())
.then(res => {
setUpdates(res.current)
})
This will mean you already have current in the provider and in the component you can access its fields directly, so you'll need to replace
<p>{updates.current}</p>
with something like
<p>{updates.temp_c}</p>
<p>{updates.humidity}</p>
Not sure, but either your updates is an object, which means you can't map, or while your API is being called, that is undefined, which is why it crashes.
If your update is an object then you can do: map function for objects (instead of arrays)
If its undefined while calling API, then you can do a simple check to see if its undefined and only map when it's not.
I am pretty new to React and I have a Select Box that I want to populate with data from an API. The API request passes an ID for that specific user and the API returns the corresponding address to that user. I need to set the ID dynamically later on, but that´s not the problem that I am currently facing; The JSON Response is fine, it is manageable to parse through the JSON Object and to log all the Key-Value-pairs to the console; however, I am having troubles when trying to set one specific key-value pair - the one that contains the address of the user - as Option in the Select Box. The Function that I have written maps through all the data and returns just one specific key-value pair, but it keeps iterating through the JSON object and hence freezes the whole application;
the returned address should be set as default option in the Select Box, the variable is called "formattedaddress".
There are definitely better ways to do that, but I don´t know how, so any hints or help would be very much appreciated, thanks in advance!
The code is the following:
import React, {Component, useContext, useEffect, useRef, useState} from 'react';
import "./formstyles.css";
//i18n
import {withNamespaces} from 'react-i18next';
//Import Breadcrumb
import { withRouter } from "react-router-dom";
import classnames from "classnames";
import EmployeesList from "../../pages/Employees/employees-list";
import EmployeeIdComponent from "./EmployeeId";
import EmployeeId from "./EmployeeId";
const SelectComponent = (props) => {
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState('');
const [data, setData] = React.useState([]);
const [mappedArr, setMappedArr] = React.useState([]);
const [formattedaddress, setAddress] = useState([])
// The following function returns the needed data
let usersWithName = Object.keys(data).map(function(key) {
JSON.stringify(data);
let newArr = Object.keys(data);
let mappedArr = newArr.map(function (i) {
return [i, data[i]];
})
let formattedaddress = mappedArr[18];
return formattedaddress
});
//This effect sets the whole function as the option value
useEffect(() => {
setAddress(
usersWithName
);
});
//The following effect fetches the data with the ID of the user and returns //the whole object
useEffect(() => {
setLoading(true);
fetch('http://tpservice:8888/api/v1/address/d472faec-4316-4040-a2cb-63cb99b5bed1')
.then((response) => response.json())
.then((data) => {
setLoading(false);
setData(data);
})
.catch((e) => {
setLoading(false);
setError('fetch failed');
});
}, []);
if (loading) {
return <p>loading..</p>;
}
if (error !== '') {
return <p>ERROR: {error}</p>;
}
return (
<React.Fragment>
<div className="clearfix">
<div className="float-left">
<div className="input-group input-group-sm">
<div className="input-group-append">
<label className="input-group-text">Adresse</label>
</div>
<select className="custom-select custom-select-sm">
<option value="1">{formattedaddress}</option>
<option value="2">Adresse 2</option>
<option value="3">Adresse 3</option>
<option value="4">Adresse 4</option>
</select>
</div>
</div>
</div>
</React.Fragment>
);
}
export default withRouter(withNamespaces()(SelectComponent));;
First thing, I do not see a dependency array for the useEffect??? where you call the setAddress, usersWithName - primary suspect of the behaviour you get.
Second thing I would advise exporting the request method in a custom hook rather than useEffect which you can use in your component e.g. something like const {data, loading, error} = useRequest(). It keeps your component free of gibberish and the hook is reusable.
Third, I believe what you want to do is mutate the response object with this method usersWithName, I would again implement it as a separate helper function and call it either in the hook (if not reusable) or just when you want to access the mutated values. Or you can use a useMemo which will store your mapped array and will recalculate values everytime the response changes.
Follow up:
hooks.js
const useReqest = ({ requestUrl }) = {
const [data, setData] = useState();
const [loading, setLoading] = useState();
const [error, setError] = useState();
useEffect(() => {
fetch(...)
.then((response) => setData(yourMutationFunction(JSON.parse(response))))
.catch((e) => setError(e))
}, [requestUrl])
return {data, loading, error};
}
You can use it in component like:
// Note this hook is execited everytime requestUrl changes or is initialised
const { data, loading, error } = useRequest({requestUrl: 'someUrl'})
Try to improve this hook and work out the third part!
After a huge amount of trial and error for a complex webGL project I have landed on a solution that will reduce the amount of re-engineering working, threejs code (from another developer) and, as this project is extremely time restrained, reduce the amount of time needed. It's also worth noting my experience of this is limited and I am the only developer left on the team.
The project current accepts a large array of random user data, which is exported from a js file and then consumed here...
import Users from "./data/data-users";
class UsersManager {
constructor() {
this.mapUserCountries = {};
}
init() {
Users.forEach(user => {
const c = user.country;
if (!this.mapUserCountries[c])
this.mapUserCountries[c] = { nbUsers: 0, users: [] };
this.mapUserCountries[c].nbUsers++;
this.mapUserCountries[c].users.push(user);
});
}
getUsersPerCountry(country) {
return this.mapUserCountries[country];
}
}
export default new UsersManager();
Here is my fetch request..
import { useState, useEffect } from "react";
const FetchUsers = () => {
const [hasError, setErrors] = useState(false);
const [users, setUsers] = useState({});
async function fetchData() {
const res = await fetch(
"https://swapi.co/api/planets/4/"
);
res
.json()
.then(res => setUsers(res))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);
return JSON.stringify(users);
};
export default FetchUsers;
I have run into lots of issues as the UserManager is a class component and if I import my fetchUsers into this file, call it and save it to a variable like so const Users = fetchUsers(); it violates hooks.
I want to be able to return a function that will return my users from the database as an array.
That will then be able to be passed into the UserManager in the same way the hard coded data is and mapped over to be actioned by LOTS of other files.
I've mocked up a small codesandbox with what the flow would be ideally but I know I need a solution outside of hooks...
https://codesandbox.io/s/funny-borg-u2yl6
thanks
--- EDIT ---
import usersP from "./data/data-users";
class UsersManager {
constructor() {
this.mapUserCountries = {};
this.state = {
users: undefined
};
}
init() {
usersP.then(users => {
this.setState({ users });
});
console.log(usersP);
this.state.users.forEach(user => {
const c = user.country;
if (!this.mapUserCountries[c])
this.mapUserCountries[c] = { nbUsers: 0, users: [] };
this.mapUserCountries[c].nbUsers++;
this.mapUserCountries[c].users.push(user);
});
}
getUsersPerCountry(country) {
return this.mapUserCountries[country];
}
}
export default new UsersManager();
console.log (UsersManager.js:16 Uncaught TypeError: Cannot read property 'forEach' of undefined
at UsersManager.init (UsersManager.js:16)
at Loader.SceneApp.onLoadingComplete [as callback] (App.js:39)
at Loader.onAssetLoaded (index.js:20)
at index.js:36
at three.module.js:36226
at HTMLImageElement.onImageLoad)
I fixed your sandbox example.
You cannot load the users synchronously (using import) as you need to make a http call to fetch the users so it's asynchronous.
As a result you can fetch the users inside the componentDidMount lifecycle method and use a state variable to store them once they are fetched
There are a couple guidelines that will help separate functions that are Hooks and functions that are Components (these are true most of the time):
1 Component functions use pascal case (start with a capital letter) and always return JSX.
2 Custom Hooks functions conventionally begin with the word "use" and never return JSX.
In your case you probably want to make a custom Hooks function that must be called in a component;
function useUserData() {
const [hasError, setErrors] = useState(false);
const [users, setUsers] = useState({});
const networkCall = useCallback(async fetchData = () => {
const res = await fetch(
"https://swapi.co/api/planets/4/"
);
res
.json()
.then(res => setUsers(res))
.catch(err => setErrors(err));
} , [])
useEffect(() => {
fetchData();
}, []);
return {users, hasError};
}
Then call that custom hook in one of your components:
function App() {
const {users, hasError} = useUserData();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div>{users}</div>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
If you then need to share that fetched data throughout your app, you can pass it down via props or the context API: https://reactjs.org/docs/context.html
(post a message if you'd like an example of this).