I have just began playing around with React hooks and am wondering how an AJAX request should look?
I have tried many attempts, but am unable to get it to work, and also don't really know the best way to implement it. Below is my latest attempt:
import React, { useState, useEffect } from 'react';
const App = () => {
const URL = 'http://api.com';
const [data, setData] = useState({});
useEffect(() => {
const resp = fetch(URL).then(res => {
console.log(res)
});
});
return (
<div>
// display content here
</div>
)
}
You could create a custom hook called useFetch that will implement the useEffect hook.
If you pass an empty array as the second argument to the useEffect hook will trigger the request on componentDidMount. By passing the url in the array this will trigger this code anytime the url updates.
Here is a demo in code sandbox.
See code below.
import React, { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const json = await response.json();
setData(json);
}
fetchData();
}, [url]);
return data;
};
const App = () => {
const URL = 'http://www.example.json';
const result = useFetch(URL);
return (
<div>
{JSON.stringify(result)}
</div>
);
}
Works just fine... Here you go:
import React, { useState, useEffect } from 'react';
const useFetch = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchUser = async () => {
const response = await fetch(url);
const data = await response.json();
const [user] = data.results;
setData(user);
setLoading(false);
};
useEffect(() => {
fetchUser();
}, []);
return { data, loading };
};
const App = () => {
const { data, loading } = useFetch('https://api.randomuser.me/');
return (
<div className="App">
{loading ? (
<div>Loading...</div>
) : (
<React.Fragment>
<div className="name">
{data.name.first} {data.name.last}
</div>
<img className="cropper" src={data.picture.large} alt="avatar" />
</React.Fragment>
)}
</div>
);
};
Live Demo:
Edit
Updated based on version change (thanks #mgol for bringing it to
my attention in the comments).
Great answers so far, but I'll add a custom hook for when you want to trigger a request, because you can do that too.
function useTriggerableEndpoint(fn) {
const [res, setRes] = useState({ data: null, error: null, loading: null });
const [req, setReq] = useState();
useEffect(
async () => {
if (!req) return;
try {
setRes({ data: null, error: null, loading: true });
const { data } = await axios(req);
setRes({ data, error: null, loading: false });
} catch (error) {
setRes({ data: null, error, loading: false });
}
},
[req]
);
return [res, (...args) => setReq(fn(...args))];
}
You can create a function using this hook for a specific API method like so if you wish, but be aware that this abstraction isn't strictly required and can be quite dangerous (a loose function with a hook is not a good idea in case it is used outside of the context of a React component function).
const todosApi = "https://jsonplaceholder.typicode.com/todos";
function postTodoEndpoint() {
return useTriggerableEndpoint(data => ({
url: todosApi,
method: "POST",
data
}));
}
Finally, from within your function component
const [newTodo, postNewTodo] = postTodoEndpoint();
function createTodo(title, body, userId) {
postNewTodo({
title,
body,
userId
});
}
And then just point createTodo to an onSubmit or onClick handler. newTodo will have your data, loading and error statuses. Sandbox code right here.
use-http is a little react useFetch hook used like: https://use-http.com
import useFetch from 'use-http'
function Todos() {
const [todos, setTodos] = useState([])
const { request, response } = useFetch('https://example.com')
// componentDidMount
useEffect(() => { initializeTodos() }, [])
async function initializeTodos() {
const initialTodos = await request.get('/todos')
if (response.ok) setTodos(initialTodos)
}
async function addTodo() {
const newTodo = await request.post('/todos', {
title: 'no way',
})
if (response.ok) setTodos([...todos, newTodo])
}
return (
<>
<button onClick={addTodo}>Add Todo</button>
{request.error && 'Error!'}
{request.loading && 'Loading...'}
{todos.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
or, if you don't want to manage the state yourself, you can do
function Todos() {
// the dependency array at the end means `onMount` (GET by default)
const { loading, error, data } = useFetch('/todos', [])
return (
<>
{error && 'Error!'}
{loading && 'Loading...'}
{data && data.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
Live Demo
I'd recommend you to use react-request-hook as it covers a lot of use cases (multiple request at same time, cancelable requests on unmounting and managed request states). It is written in typescript, so you can take advantage of this if your project uses typescript as well, and if it doesn't, depending on your IDE you might see the type hints, and the library also provides some helpers to allow you to safely type the payload that you expect as result from a request.
It's well tested (100% code coverage) and you might use it simple as that:
function UserProfile(props) {
const [user, getUser] = useResource((id) => {
url: `/user/${id}`,
method: 'GET'
})
useEffect(() => getUser(props.userId), []);
if (user.isLoading) return <Spinner />;
return (
<User
name={user.data.name}
age={user.data.age}
email={user.data.email}
>
)
}
image example
Author disclaimer: We've been using this implementation in production. There's a bunch of hooks to deal with promises but there are also edge cases not being covered or not enough test implemented. react-request-hook is battle tested even before its official release. Its main goal is to be well tested and safe to use as we're dealing with one of the most critical aspects of our apps.
Traditionally, you would write the Ajax call in the componentDidMount lifecycle of class components and use setState to display the returned data when the request has returned.
With hooks, you would use useEffect and passing in an empty array as the second argument to make the callback run once on mount of the component.
Here's an example which fetches a random user profile from an API and renders the name.
function AjaxExample() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUser(data.results[0]);
});
}, []); // Pass empty array to only run once on mount.
return <div>
{user ? user.name.first : 'Loading...'}
</div>;
}
ReactDOM.render(<AjaxExample/>, document.getElementById('app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
I find many wrong usages of useEffect in the answers above.
An async function shouldn't be passed into useEffect.
Let's see the signature of useEffect:
useEffect(didUpdate, inputs);
You can do side effects in didUpdate function, and return a dispose function. The dispose function is very important, you can use that function to cancel a request, clear a timer etc.
Any async function will return a promise, but not a function, so the dispose function actually takes no effects.
So pass in an async function absolutely can handle your side effects, but is an anti-pattern of Hooks API.
Here's something which I think will work:
import React, { useState, useEffect } from 'react';
const App = () => {
const URL = 'http://api.com';
const [data, setData] = useState({})
useEffect(function () {
const getData = async () => {
const resp = await fetch(URL);
const data = await resp.json();
setData(data);
}
getData();
}, []);
return (
<div>
{ data.something ? data.something : 'still loading' }
</div>
)
}
There are couple of important bits:
The function that you pass to useEffect acts as a componentDidMount which means that it may be executed many times. That's why we are adding an empty array as a second argument, which means "This effect has no dependencies, so run it only once".
Your App component still renders something even tho the data is not here yet. So you have to handle the case where the data is not loaded but the component is rendered. There's no change in that by the way. We are doing that even now.
Related
I have a component where-in I need to fetch some data and render it. The component gets rendered initially. The problem I'm facing is when the handler function switchDocumentType is called after clicking the button for a particular type, the whole component gets unmounted/un-rendered.
While debugging on my own I found this happens after setDocumentType is run inside event handler function.
What is wrong in the below code snippet that could possibly cause this issue? I can see the useEffect is not going in infinite-loop as well.
Code snippet:
import * as React from 'react';
const MyComponent = (props) => {
const [documentType, setDocumentType] = React.useState('alpha');
const [documentData, setDocumentData] = React.useState('');
const types = ['alpha', 'beta', 'gamma'];
React.useEffect(() => {
myDataFetch('https://example.com/foo/?bar=123').then(async (response) => {
const data = await response.json();
setDocumentData(data.terms); // html string
const myDiv = document.getElementById('spacial-div');
myDiv.innerHTML = data; // need to render raw HTML inside a div
});
}, [documentType]);
const switchDocumentType = (type) => {
setDocumentType(type);
// send some analytics events
};
const convertToPDF = () => {
// uses documentData to generate PDF
};
return (
<div className="container-div">
{types.map((type) => {
return (
<button key={type} onClick={(type) => switchDocumentType(type)}>
{type}
</button>
);
})}
<div id="special-div" />
</div>
);
};
export default MyComponent;
You shouldn't edit the DOM directly. React has two DOMs, a virtual DOM and a real DOM. Rendering can be a bit finicky if you decide to edit the real DOM.
You can parse html safely, by using html-react-parser. This is the best way to do it, because it becomes part of the react tree whereas dangerouslySetInnerHTML will replace the entire HTML to flush changes to the DOM. With reconciliation, it can create exponential load times.
It will also sanitize your inputs, you know.. for safety. :)
import parse from 'html-react-parser';
const SpecialDiv = ({html}) => {
const reactElement = parse(html);
return reactElement
}
If you decide that you must use dangerouslySetInnerHTML you can do it as so:
const [someHTML, setSomeHTML] = useState(null)
const someFunction = async() => {
const response = await getData();
const data = await response.json();
setSomeHTML(data);
}
return(
<div>
{someHTML && <div dangerouslySetInnerHTML={{__html: someHTML}} id="special-div"/>}
</div>
)
That being said, I would say that by allowing this, you open yourself up to the possibility of a XSS attack, without properly parsing and purifying your inputs.
Do not use useEffect as handler, use useEffect hooks for initializations.
Instead of using/setting innerHtml, let react do it for you.
I suppose you have myDataFetch defined somewhere and I don't see your data fetch using the type.
Anyways, try to use the modified code below.
import * as React from 'react';
const MyComponent = (props) => {
const [documentType, setDocumentType] = React.useState('alpha');
const [documentData, setDocumentData] = React.useState('');
const types = ['alpha', 'beta', 'gamma'];
const fetchData = async () => {
const response = await myDataFetch('https://example.com/foo/?bar=123')
const data = await response.json();
setDocumentData(data);
}
React.useEffect(() => {
fetchData();
}, []);
const switchDocumentType = async (e, type) => {
e.preventDefault();
setDocumentType(type);
await fetchData();
// send some analytics events
};
return (
<div className="container-div">
{types.map((type) => {
return (
<button key={type} onClick={(e) => switchDocumentType(e, type)}>
{type}
</button>
);
})}
<div id="special-div">{documentData}</div>
</div>
);
};
export default MyComponent;
Not sure why but placing debuggers before state update causes this issue, not only for this component, but for all the other components I tried with. Seems to be an issue either with debugger or React. Removing debuggers solved the issue.
Also, now I'm returning a cleanup function inside useEffect as pointed out in some stack-overflow posts. I also refactored the code as suggested by #iaq and #sheepiiHD to follow React best practices.
Updated code:
import * as React from 'react';
const MyComponent = (props) => {
const [documentType, setDocumentType] = React.useState('alpha');
const [documentData, setDocumentData] = React.useState('');
const types = ['alpha', 'beta', 'gamma'];
const fetchData = async () => {
const response = await myDataFetch('https://example.com/foo/?bar=123')
const data = await response.json();
setDocumentData(data);
}
React.useEffect(() => {
fetchData();
return () => {
setDocumentType('');
setDocumentData('');
};
}, []);
const switchDocumentType = async (e, type) => {
e.preventDefault();
setDocumentType(type);
await fetchData();
// send some analytics events
};
return (
<div className="container-div">
{types.map((type) => {
return (
<button key={type} onClick={(e) => switchDocumentType(e, type)}>
{type}
</button>
);
})}
<div id="special-div" dangerouslySetInnerHTML={{__html: documentData.terms}} />
</div>
);
};
export default MyComponent;
I've been trying to use the data I get from an Async function inside of another function I use to display HTML on a react project. I have made several attempts but nothing seems to work for me. Hope any of you could help me. Please correct me if I did anything wrong.
I've tried it with a useEffect as well:
import React, { useState, useEffect } from 'react';
import { getGenres } from './api/functions';
const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const genres = await getGenres('tv');
updateData(genres);
}
getData();
}, []);
return data && <Screen data={data} />
}
const Screen = ({data}) => {
console.log({data}); //logs 'data: undefined' to the console
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
</div>
);
}
export default Screen;
The Error I get from this is: {data: undefined}.
The getGenres function that makes the HTTP Request:
const apiKey = 'key';
const baseUrl = 'https://api.themoviedb.org/3';
export const getGenres = async (type) => {
const requestEndpoint = `/genre/${type}/list`;
const requestParams = `?api_key=${apiKey}`;
const urlToFetch = baseUrl + requestEndpoint + requestParams;
try {
const response = await fetch(urlToFetch);
if(response.ok) {
const jsonResponse = await response.json();
const genres = jsonResponse.genres;
return genres;
}
} catch(e) {
console.log(e);
}
}
I want to use the data inside my HTML, so the H1 for example.
Once again, haven't been doing this for a long time so correct me if I'm wrong.
There are a few conceptual misunderstandings that I want to tackle in your code.
In modern React, your components should typically render some type of jsx, which is how React renders html. In your first example, you are using App to return your genres to your Screen component, which you don't need to do.
If your goal is to fetch some genres and then ultimately print them out onto the screen, you only need one component. Inside that component, you will useEffect to call an asynchronous function that will then await the api data and set it to a react state. That state will then be what you can iterate through.
When genres is first rendered by react on line 6, it will be undefined. Then, once the api data is retrieved, React will update the value of genre to be your array of genres which will cause the component to be re-rendered.
{genres && genres.map((genre) ... on line 20 checks to see if genres is defined, and only if it is, will it map (like looping) through the genres. At first, since genres is undefined, nothing will print and no errors will be thrown. After the genres are set in our useEffect hook, genres will now be an array and we can therefore loop through them.
Here is a working example of your code.
import React, { useState, useEffect } from "react";
import { getGenres } from "./api/functions";
function App() {
const [genres, setGenres] = useState();
useEffect(() => {
async function apiCall() {
const apiResponse = await getGenres("tv");
console.log(apiResponse);
setGenres(apiResponse);
}
apiCall();
}, []);
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
{genres && genres.map((genre) => <div key={genre}>{genre}</div>)}
</div>
);
}
export default App;
You should use a combination or useEffect and useState
You should use useEffect to launch the async get, without launching it each rerendering. If a async function is used to get some data from outside the component, this is called 'side effect' so use useEffect.
You should use useState to react to changes on theses side effects, re-rendering the component to get the data in the dom.
In the next example, Im using a dummy async function getGenres which returns an array of genres.
Here is an example and a WORKING EXAMPLE :
const {useState, useEffect} = React;
async function getGenres() {
var promise = new Promise(function(resolve, reject) {
window.setTimeout(function() {
resolve( ['genre1', 'genre2']);
});
});
return promise;
}
const Screen = () => {
const [genres, setGenres] = useState([])
useEffect(
() => {
getGenres().then(
res => setGenres(res)
)
}, [getGenres]
)
return (
<ul>
{
genres.map(
i => <li>{i}</li>
)
}
</ul>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Screen/>)
I had a basic useFetch hook implementation that defined a fetchData function which would setData to some JSON if successful, then I would called it on useEffect with no dependencies and the hook returned the stateful data value. I found that this was not ideal, because I wanted to fetch things dynamically on events.
So Instead, I changed the useFetch hook to simply return the fetchData function reference along with the data, and no longer call fetchData inside the hook.
const useFetch = () => {
const [data, setData] = useState([]);
const fetchData = async (url) => {
try {
const response = await fetch(url);
if (response.ok) {
const jsonData = await response.json();
setData(jsonData);
} else {
throw new Error(response.status);
}
} catch (err) {
console.error(err);
}
};
return { fetchData, data };
};
This however introduced problems where I use this hook. I've never used this pattern before so I don't really know what I'm doing, but I'm unable to do stuff with the data value after calling the fetch function. Here's basically how I'm using the hook in another functional component:
const [displayedItems, setDisplayedItems] = useState([]);
const { fetchData, data } = useFetch();
useEffect(() => {
fetchData(urlGoesHere);
}, []);
useEffect(() => {
setDisplayedItems(data);
console.log(displayedItems);
}, [data]);
This is ugly, and it doesn't work. I tried putting them all in one useEffect, but that also doesn't work. Sometimes, when I live reload in CRA, I can see data being logged, but typically when my page loads, data just stays undefined. So I basically created another problem by changing useFetch from actually using the fetch function (which has the downside of not being able to be called in my regular functions and event callbacks), and now I can't seem to even render anything.
I'm pretty new to React and async stuff so I'd appreciate a response that takes that into consideration. If there's a much better pattern for the kind of thing I'm trying to do, I'd love to hear it, but I'd like to keep it in vanilla react land, so no libraries, etc. Again, the reason I'm returning a reference to the fetch function is because I want to be able to use it in callbacks and stuff.
Thanks all!
Edit: It sort of works if I check for the truthiness of data in the second useEffect, but anyhow, can this implementation be better?
useEffect(() => {
fetchData(urlGoesHere);
}, []);
useEffect(() => {
if (data) {
setDisplayedItems(data);
console.log(displayedItems);
}
}, [data]);
Does this mean I have to use 2 whole useEffects every time I want to fetch something on load?
You can return more properties than just the data from the hook in order to help you make an informed choice about what and when to render. A common pattern in many simple useFetch hooks like the one you've asked about is to return data, error, and isLoading, so that you can declaratively render the UI you intend to based on a combination of those states, still eagerly fetching the data after the component first renders.
If you need more control over when the data is fetched (e.g. you mentioned in reaction to an "event"), just use the hook in a child component, and conditionally render that child component based on your "event". Below is a typical example of a simplistic useFetch hook (which you can see working in the code snippet):
Here's a link to just the hook in the TypeScript Playground, which you can use to access and copy the transpiled JavaScript if you can't or don't want to use TypeScript.
<div id="root"></div>
<script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.16.4/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
/**
* The following line is here because this Stack Overflow snippet uses the
* UMD module for React. In your code, you'd use the commented `import` lines
* below it.
*/
const {useEffect, useState} = React;
// import ReactDOM from 'react-dom';
// import {useEffect, useState} from 'react';
// import type {ReactElement} from 'react';
type FetchData<T> = {
data: T | undefined;
error: Error | undefined;
isLoading: boolean;
};
function useFetch <T = unknown>(url: string): FetchData<T> {
const [data, setData] = useState<T | undefined>();
const [error, setError] = useState<Error | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
const ac = new AbortController();
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(url, {signal: ac.signal});
if (!response.ok) throw new Error(String(response.status));
const result = await response.json() as T;
setData(result);
setError(undefined);
}
catch (ex: unknown) {
setError(ex instanceof Error ? ex : new Error(String(ex)));
}
setIsLoading(false);
};
fetchData();
return () => ac.abort();
}, [url, setData, setError, setIsLoading]);
return {data, error, isLoading};
}
type User = { username: string };
function Example (): ReactElement {
const url = 'https://jsonplaceholder.typicode.com/users';
const {data, error, isLoading} = useFetch<User[]>(url);
return (
<div>
<h1>Users</h1>
{
isLoading
? (<div>Loading users...</div>)
: null
}
{
error
? (<div>There was an error loading the data ({error.message})</div>)
: null
}
{
data
? (
<ul>
{data.map(({username}, index) => (
<li key={`${index}-${username}`}>{username}</li>
))}
</ul>
)
: null
}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
I made a custom react hook, which has a useEffect and, for now, returns a set of different states. It's a hook for axios, and the gist of it is this:
export default function useAxios({ url, data = {}, method = "GET"} ) {
var [loading, setLoading] = useState(true)
var [data, setData] = useState(null)
useEffect(function() {
(async function() {
// do axios request and set loading and data states
})()
return () => // cleanup function that cancels axios request
}, [])
return {
loading,
data
}
}
Now, in a simple component I can easily use this custom hook - but my question is: What if I want to use my hook inside an event handler, say:
export default MyComponent() {
function handleSubmit(e) {
var { data } = useAxios({
url: "/my-end-point",
data: {
testInput: e.target.testInput.value
}
})
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="testInput" />
<button type="submit">Submit</button>
</form>
)
}
Problem is my useAxios hook has a useEffect, and so I cannot use it inside a non-component function, i.e. handleSubmit. So what is the work around? Is there even one? Thanks in advance.
As to React's
Only Call Hooks from React Functions, you should always:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks.
Fail to satisfy these two rules leads to unexpected render result out of React.
With those rules in mind, you should return a submitHanlder from react hook instead of just passing the hook function into another component as a callback function.
I might guess that your intention is to trigger the axios request on the submit event. If so, it is possible to achieve that without passing whole hook into event handler.
First of all, as the rules say, you have to make sure your hook got called in every render. So the MyComponent can be rewrite in the below way:
export default function MyComponent() {
var startRequest = useAxios({url: "/my-end-point"}) //<---- useAxios now returns the startRequest function, and will always be called on every render
return (
<form onSubmit={(e) => {
e.preventDefault()
startRequest({testInput: e.target.testInput.value}) // <----- call your startRequest here in the submit hanlder
.then(data => {
//process your data here
})
}}>
<input type="text" name="testInput" />
<button type="submit">Submit</button>
</form>
)
}
Please note that now the hook returns a function startRequest which you can put in your handler, and trigger that handler any time appropriated.
And rearrange your hook's code like below:
export function useAxios({ url, method = "GET"} ) {
var [loading, setLoading] = useState(true)
// <------ no setData here
var startRequest = async function(body = {}) { // <------ Move your input here
// do axios request and set loading and data states
setLoading(true)
await data = axios.post(body)
setLoading(false)
return data // <------- return data as promise
}
var cancelRequest = () => // cleanup function that cancels axios request
useEffect(function() {
return cancelRequest
}, []) // useEffect only helps your cancel request on unmounted.
return startRequest
}
The useEffect now only helps you cleanup axios request without the need to start one, since firing a request should be an event handler's job.
And since the data return by axios is in a promise, you don't need to explicitly setData to store your response data so I removed the line of useState(null).
I would take a look at popular libraries like SWR (useSWR) and apollo-client (useQuery). They're approach is something like this when making get requests
const MyComponent = () => {
const [shouldSkip, setShouldSkip] = useState(true);
const queryResult = useQuery('my-url', {skip: shouldSkip});
const handleSubmit = () => {
setShouldSkip(false);
// this will cause the component to rerender, and skip will now be false
}
}
When making post requests, its something like this:
const MyComponent = () => {
//useMutation returns a callable function whenever you want
const callFunction = useMutation('my-url');
const handleSubmit = () => {
await callFunction()
}
}
You can also take a look at axios-specific hooks like https://github.com/simoneb/axios-hooks, another common pattern they use is to include a refetch function as a result of the hook, that can be called at anytime (like in an event handler)
The point of the hook is not to make the request for you, the point of the hook is to communicate the internal state of stuff (the axios request, in your case) to the component, so that you can render stuff based around that state (like loading states, or the data).
In your case, you can change the value of the query based on the component state, and have the hook return the data to the component based on its parameters. Something like this:
const useAxios = ({ query }) => {
var [loading, setLoading] = useState(true)
var [data, setData] = useState(null)
useEffect(function () {
(async function () {
setLoading(true)
// do axios request and set loading and data states
const request = await axios.get('endpoint', { query })
setData(request.data)
setLoading(false)
})()
return () => { }// cleanup function that cancels axios request
}, [])
return {
loading,
data
}
}
const Component = () => {
const [query, setQuery] = useState('')
const { loading, data } = useAxios({ query });
const submitHandler = (event) => { setQuery(event.target.testInput.value) }
return (
<>
<form onSubmit={submitHandler}>
<input name="testInput" />
<input type="submit" />
</form>
{loading && (
<>a spinner</>
)}
{data && (
<DataRenderer data={data} />
)}
</>
)
}
I'm using a React Hook to fetching data from a JSON object by using the native fetch method.
const COVID_API_URL = "https://api.covid19api.com/summary";
const [infected, setInfected] = useState([]);
async function fetchData() {
const response = await fetch(COVID_API_URL);
response.json().then(response => setInfected(response));
}
useEffect(() => {
fetchData();
}, []);
console.log(infected.Global.TotalConfirmed);
When I console.log the value from TotalConfirmed, I get the correct result, but when I refresh the browser, I keep getting:
TypeError: Cannot read property 'TotalConfirmed' of undefined
Anyone know why this is happening? I'm using Gatsby.js default starter, does this have anything to do with this?
The problem here is the value for infected state arrives asynchronously. What you can do is using useEffect hook.
Try as the following:
useEffect(() => {
console.log(infected.Global.TotalConfirmed);
}, [infected]);
By doing this useEffect callback will be triggered once infected value is changing - API call responses - so after setInfected(response) updates the value of infected state.
I hope this helps!
The first time you access infected it is still undefined, all you need to do is validate the variable before using it. You don't need another useEffect.
console.log(infected && infected.Global && infected.Global.TotalConfirmed);
const {useState, useEffect} = React;
const COVID_API_URL = "https://api.covid19api.com/summary";
function App() {
const [infected, setInfected] = useState([]);
const [isLoading, setIsLoading] = useState(true);
function fetchData() {
fetch(COVID_API_URL)
.then(jsonResponse => jsonResponse.json())
.then(response => {
setIsLoading(false);
setInfected(response);
})
.catch(e => {
console.log("error", e);
setIsLoading(false);
});
}
useEffect(() => {
fetchData();
}, []);
console.log(infected && infected.Global && infected.Global.TotalConfirmed);
return (
<div className="App">
<h1>COVID-19 Summary</h1>
{isLoading && <p>Getting data...</p>}
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You can add a new state like in my example isFetched then set it's default value to false
const [isFetched,setIsFetched] = useState(false);
and then use the setIsFetched function after your setInfected(response)
and finally you can check if isFetched is true then use your api data(infected state) in your code.
const COVID_API_URL = "https://api.covid19api.com/summary";
const [infected, setInfected] = useState([]);
const [isFetched,setIsFetched] = useState(false);
async function fetchData() {
const response = await fetch(COVID_API_URL);
response.json().then(response =>
{
setInfected(response));
setIsFetched(true);
}
}
useEffect(() => {
fetchData();
}, []);
then when you want to show api data on page you do:
{
isFetched ?
// use api data : null
}
I was also having this issue, even after using axios. Its easy to fix, you just need to add if else statement before return. like this
`if(myArray.length > 0){
return (data and everything)}
else{return <h1>Loading</h1>}`