I wanted to know what I'm doing wrong.
I tried to display the data that I'm receiving from this API call and I'm encountering an error.
When I check with React DevTool, my state got the data.
Displaying {total.ts} is working but not with {total.data.active} when I try to acces data from this object.
Btw I wanted to know too, I'm receiving an object: useState({}) is correct ?
Thanks for your futures answers and helping me, it's probably nothing difficult.
import React, { useEffect, useState } from "react";
import Axios from "axios";
function Total() {
const [total, setTotal] = useState({});
useEffect(() => {
Axios.get("https://covid2019-api.herokuapp.com/v2/total").then(
(response) => {
setTotal(response.data);
}
);
}, []);
return (
<>
<h1>Hello from Total</h1>
<div className="global-container">
<div className="title-container"></div>
<div className="data-container">{total.data.active}</div>
<div className="date-container">{total.ts}</div>
</div>
</>
);
}
export default Total;
Your issue is likely that total.data.active will generate a js error because total.data is undefined until you get your ajax call response. A check on this will probably fix the issue.
Here is an example of how it could be done (I used fetch instead of Axios but it's not the important part):
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [total, setTotal] = useState({});
useEffect(() => {
fetch("https://covid2019-api.herokuapp.com/v2/total")
.then((response) => response.json())
.then((responseBody) => {
setTotal(responseBody);
});
}, []);
return (
<>
<h1>Hello from Total</h1>
<div className="global-container">
<div className="title-container"></div>
<div className="data-container">
{total && total.data ? total.data.active : ''}
</div>
<div className="date-container">{total.ts}</div>
</div>
</>
);
}
You should add condition while data is loading, because your api call is asynchronous
import React, { useEffect, useState } from "react";
import Axios from "axios";
function Total() {
const [total, setTotal] = useState(null);
useEffect(() => {
Axios.get("https://covid2019-api.herokuapp.com/v2/total").then(
(response) => {
setTotal(response.data);
}
);
}, []);
return (
<>
<h1>Hello from Total</h1>
{
total ? (
<div className="global-container">
<div className="title-container"></div>
<div className="data-container">{total.data.active}</div>
<div className="date-container">{total.ts}</div>
</div>
) : (
<div>Loading...</div>
)
}
</>
);
}
export default Total;
`
Related
I have an issue where my page is trying to render before the data is available. I have async awaits in place, however, the page gets an error saying data is undefined. When I comment out my page elements and check react dev tools I can see the data object in full, so I know the data request is working.
I need to put in a check for the data and if present then render but as a new developer I am not sure how to implement this in my code.
import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getDoc, doc } from "firebase/firestore";
import { db } from "../api/auth/firebase/config";
import Head from "next/head";
import ArtistHeader from "../../components/ArtistHeader";
import UploadButton from "../../components/UploadButton";
import styles from "../../styles/artistPage.module.css";
export default function Artist() {
const { data: session, status, loading } = useSession();
const [artist, setArtist] = useState();
const router = useRouter();
const artistId = router.query.artistId;
const fetchArtist = async () => {
const artistRef = doc(db, "users", `${artistId}`);
const docSnap = await getDoc(artistRef);
setArtist(docSnap.data());
};
useEffect(() => {
if (!router.isReady) return;
console.log(artistId);
if (status === "unauthenticated") {
router.push("/auth/signin");
}
fetchArtist();
}, [status, loading, router]);
return (
<section className={styles.wrapper}>
<Head>
<title>{artist.screenName}</title>
</Head>
<div className={styles.artistPage}>
<ArtistHeader artist={artist} />
<div className={styles.songContainer}>
<UploadButton />
</div>
</div>
</section>
);
}
Thanks in advance for help.
use optional chaining. This will prevent you from getting undefined error.
see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
On the other hand you can do the following:
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
const fetchArtist = async () => {
setIsLoading(true)
const artistRef = doc(db, "users", `${artistId}`);
try{
const docSnap = await getDoc(artistRef);
setArtist(docSnap.data());
}catch(e){
setIsError(true)
}
setIsLoading(false)
};
if(isLoading && !artist){
return (
<h2>Loading...</h2>
)
}
if(!isLoading && isError){
return (
<h2>Something went wrong</h2>
)
}
return (
<section className={styles.wrapper}>
<Head>
<title>{artist?.screenName}</title>
</Head>
<div className={styles.artistPage}>
<ArtistHeader artist={artist} />
<div className={styles.songContainer}>
<UploadButton />
</div>
</div>
</section>
)
But I would prefer react-query for server state management. It handles all your loading | revalidation | caching and more.
Check out https://tanstack.com/query/v4/docs/adapters/react-query
Let's make it simple with useQuery hook from react-query
import { useQuery } from '#tanstack/react-query'
const fetchArtist = async (artistId: string) => {
const artistRef = doc(db, "users", `${artistId}`);
return getDoc(artistRef);
};
function Artist() {
const query = useQuery(['artist', artistId], fetchArtist)
const {isLoading, isError, data} = query
if(isLoading){
return (
<h2>Loading...</h2>
)
}
if(isError && !data){
return (
<h2>Something went wrong</h2>
)
}
return (
<section className={styles.wrapper}>
<Head>
{/* optional chaining (?.) */}
<title>{data?.artist?.screenName}</title>
</Head>
<div className={styles.artistPage}>
<ArtistHeader artist={data?.artist} />
<div className={styles.songContainer}>
<UploadButton />
</div>
</div>
</section>
)
}
// _app.jsx
import { Hydrate, QueryClient, QueryClientProvider } from '#tanstack/react-query'
export default function MyApp({ Component, pageProps }) {
const [queryClient] = React.useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<Component {...pageProps} />
</Hydrate>
</QueryClientProvider>
)
}
You can use getServerSideProps to call API on the server. Whenever data is ready, the page will start loading on the client-side.
import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getDoc, doc } from "firebase/firestore";
import { db } from "../api/auth/firebase/config";
import Head from "next/head";
import ArtistHeader from "../../components/ArtistHeader";
import UploadButton from "../../components/UploadButton";
import styles from "../../styles/artistPage.module.css";
export default function Artist({ data }) {
const { data: session, status, loading } = useSession();
const artist = data; //get data from the server
const router = useRouter();
useEffect(() => {
if (status === "unauthenticated") {
router.push("/auth/signin");
}
}, [status, loading, router]);
return (
<section className={styles.wrapper}>
<Head>
<title>{artist.screenName}</title>
</Head>
<div className={styles.artistPage}>
<ArtistHeader artist={artist} />
<div className={styles.songContainer}>
<UploadButton />
</div>
</div>
</section>
);
}
export async function getServerSideProps(context) {
const artistId = context.params.artistId;
const artistRef = doc(db, "users", `${artistId}`);
const docSnap = await getDoc(artistRef);
const data = docSnap.data();
return { props: { data: data || null } }
}
You can use a state isLoading. The initial value of isLoading will be false. Inside useEffect before fetching data set isLoading value as true and after completing fetching set isLoading as false. Now use conditional rendering if isLoading then render a Loader component else render jsx with data.
I am not able to retrieve content from API every time I reload my page it shows error, please see the attached image, I wanted to find the weather details using Weather API and right now I am using static latitude and longitude.
import React, { useState, useEffect } from "react";
import axios from "axios";
import { FaRegSun } from "react-icons/fa";
import "./stylesheets/stylesheets.css";
function WeatherApp1() {
const [weatherData2, setWeatherData2] = useState({});
const API_endpoint2 = `https://api.openweathermap.org/data/2.5/onecall?`;
const API_key = `2a63c27d8ba0b0d14c9e5d59f39ee1ba`;
useEffect(() => {
async function getSecondObject() {
const response = await axios.get(
`${API_endpoint2}lat=28.4360704&lon=77.021184&units=metric&appid=${API_key}`
);
setWeatherData2(response.data);
}
getSecondObject();
}, []);
return (
<div className="mainDiv">
<div className="heading">
<h1>
<FaRegSun /> Weather
</h1>
</div>
{weatherData2.current.temp}
</div>
);
}
export default WeatherApp1;
https://i.stack.imgur.com/oqr7i.jpg
The problem with your code is that you're trying to render {weatherData2.current.temp} before the data is returned from the weather API and that's why your weatherData2 will be undefined while rendering.
You can add a loading state for checking if the data is rendering or already rendered.
You can try below code:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { FaRegSun } from "react-icons/fa";
import "./stylesheets/stylesheets.css";
function WeatherApp1() {
const [loading, setLoading] = useState(true) // Loading state
const [weatherData2, setWeatherData2] = useState({});
const API_endpoint2 = `https://api.openweathermap.org/data/2.5/onecall?`;
const API_key = `2a63c27d8ba0b0d14c9e5d59f39ee1ba`;
useEffect(() => {
async function getSecondObject() {
const response = await axios.get(
`${API_endpoint2}lat=28.4360704&lon=77.021184&units=metric&appid=${API_key}`
);
setWeatherData2(response.data);
setLoading(false) // Setting the loading state to false after data is set.
}
getSecondObject();
}, []);
return (
<div className="mainDiv">
<div className="heading">
<h1>
<FaRegSun /> Weather
</h1>
</div>
{/* Checking for loading state before rendering the data */}
{loading ? (
<p>Loading...</p>
) : (
weatherData2.current.temp
)}
</div>
);
}
export default WeatherApp1;
I am building a simple recipe app and I have a problem with fetching my data from the API, because the code seems to run on every render and I do not even understand why it re-runs since I found that if I add the dependency array, it should run only once, right ?
App.js
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={recipesList} getRecipes={setRecipesList} />
</div>
);
}
export default App;
Recipes.js
import React, {useEffect, useState} from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = (props) => {
useEffect( () => {
if (props.recipesList.length === 0) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.getRecipes(result.recipes);
}
)
}
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [props])
const recipeComponent = props.recipesList.map( (item) => {
return <Recipe className="recipe" info={item}/>
})
return(
<div className="recipes">
{recipeComponent}
<h1>Hello</h1>
</div>
)
}
export default Recipes;
Components will re-render every time your the props or state changes inside of the component.
I would recommend keeping the fetching logic inside of the Recipes component, because A: its recipe related data, not app related data. And B: this way you can control the state in Recipes instead of the props. This will give you more control on how the component behaves instead of being dependent on the parent component.
In the useEffect hook, leave the dependency array empty. This will cause the component to render, call useEffect only the first time, load your data and then render the recipes without re-rendering further.
import React, { useEffect, useState } from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = () => {
const [recipesList, setRecipesList] = useState([]);
useEffect(() => {
fetch("myapi.com/blablabla")
.then((res) => res.json())
.then((result) => {
setRecipesList(result.recipes);
});
return () => console.log("unmounting");
}, []);
// On the first render recipeComponents will be empty.
const recipeComponents = recipesList.map((item) => <Recipe className="recipe" info={item}/>)
return (
<div className="recipes">
{recipeComponents}
<h1>Hello</h1>
</div>
);
};
export default Recipes;
try this code :
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
const getListPropd = (e) => {
setRecipesList(e)
}
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={(e) => getListPropd (e)} getRecipes={setRecipesList} />
</div>
);
}
export default App;
const [checkData , setCheckData ] = useState(true)
useEffect( () => {
if (checkData) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.recipesList(result.recipes);
}
if(props.recipesList.length > 0) {
setCheckData(false)
}
)
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [checkData])
the useEffect hook uses an empty dependency array, [] if it should ONLY run once after component is mounted. This is the equivalent of the old lifecycle method componentDidMount()
If you add a non-empty dependency array, then the component rerenders EVERY time this changes. In this case, every time your component receives new props (i.e. from a parent component, this triggers a reload.
see more info here https://reactjs.org/docs/hooks-effect.html , especially the yellow block at the bottom of the page
Happy coding!
I am using Easy Peasy State management for React. I would like to create multiple Axios call from one store location and import it in each page there where I need to show the correct data. I am trying to fetch a JSON placeholder data for example and use that inside a component to push it to the state using Hooks.
But I get the following error:
model.js:14 Uncaught (in promise) TypeError: actions.setTodos is not a function
at model.js:14
Can someone help me out? What am I doing wrong?
My code for the store (model.js):
import { thunk } from 'easy-peasy';
export default {
todos: [],
fetchTodos: thunk(async actions => {
const res = await fetch(
'https://jsonplaceholder.typicode.com/todos?_limit=10'
);
const todos = res.json();
actions.setTodos(todos);
}),
};
My Page component Contact:
import React, { useState, useEffect } from 'react';
import { useStoreActions } from 'easy-peasy';
import ReactHtmlParser from 'react-html-parser';
import { API_URL } from 'constants/import';
// import axios from 'axios';
const Contact = () => {
const [contactPage, setContactPage] = useState([]);
const { page_title, page_content, page_featured_image } = contactPage;
const fetchTodos = useStoreActions(actions => actions.fetchTodos);
useEffect(() => {
fetchTodos();
}, []);
return (
<section className="contact">
<div className="page">
<div className="row">
<div className="col-xs-12">
<h3 className="section__title">{page_title}</h3>
{ReactHtmlParser(page_content)}
{page_featured_image && (
<img src={API_URL + page_featured_image.path} />
)}
</div>
</div>
</div>
</section>
);
};
export default Contact;
You need to use action.
import { action, thunk } from "easy-peasy";
export default {
fetchTodos: thunk(async (actions, payload) => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/todos?_limit=10"
);
const todos = res.json();
actions.setTodos(todos);
}),
todos: [],
setTodos: action((state, payload) => {
console.log("---->>> payload!")
state.todos = payload
}),
};
I usually use it like this, it works perfectly for me.
Below is the code for my biggest nightmare yet. I keep on getting the error that the apiData.map is not a function. Any body that can help please.
I also need to know why ApiGetData do not use react please.
I do get the api data but seems that I'm importing it incorrectly to ClassFilmData and I get the .map error. All help will be appreciated.
Tried to export films, ApiGetData in various way. Help received from other platforms was implemented but did not solve the problem. Searches - other swapi projects, import data react, sandbox, repo and other platforms
// import React from 'react';
import { ApiToGet } from "./ApiToGet";
const ApiGetData = async function() {
try {
const films = await Promise.all(
ApiToGet.map(url => fetch(url).then(resp => resp.json()))
);
console.log("film title - ", films.results);
return films;
} catch (err) {
console.log("oooooooops", err);
}
};
ApiGetData();
export default ApiGetData;
import React from "react";
import FilmsInfo from "./FilmsInfo";
const FilmsLoop = ({ apiData }) => {
return (
<div className="tc f1 unknown">
{apiData.map((answers, i) => {
return (
<FilmsInfo
key={i}
// title={ apiData.films.results[i].title }
/>
);
})}
</div>
);
};
export default FilmsLoop;
import React, { Component } from "react";
import FilmsLoop from "./FilmsLoop";
import ApiGetData from "./ApiGetData";
class ClassFilmData extends Component {
render() {
return (
<div>
<p className="tc f1">Wim - classfilmdata</p>
<FilmsLoop apiData={ApiGetData} />
</div>
);
}
}
export default ClassFilmData;
import React from "react";
const FilmsInfo = () => {
return (
<div className="tc bg-light-blue dib br3 pa3 ma3 grow bw2 shadow-5">
<p>Planet</p>
<p>FilmsInfo.js</p>
</div>
);
};
export default FilmsInfo;
That is because apiData is really ApiGetData which is a promise.
If you're trying to use the array returned by resolving this promise, you'll have to do something like this:
class ClassFilmData extends Component {
componentDidMount() {
const apiData = await ApiGetData();
this.setState({ apiData });
}
render() {
return(
<div>
<p className="tc f1">Wim - classfilmdata</p>
{this.state.apiData && <FilmsLoop apiData={ this.state.apiData }/> }
</div>
);
}
}