Note: If you want to support SSR/SSG for SEO, use framework specific api from React-router/Remix, Next.js or Gatsby.
For React version >=19
We now have nice loader and form action api:
Loading data:
function Comments({ dataPromise }) {
const data = use(dataPromise);
return <div>{JSON.stringify(data)}</div>;
}
async function loadData() {
return { data: "some data" };
}
export function Index() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments dataPromise={loadData()} />
</Suspense>
);
}
Form actions
async function updateDataAndLoadNew() {
// update data
// load new data
// return new data
return { data: "some data" };
}
export default function Index() {
const [state, action, isPending] = useActionState(
async (prev, formData) => {
return updateUserAndLoadNewData();
},
{ data: "initial state, no data" } // can be null
);
if (state.error) {
return `Error ${state.error.message}`;
}
return (
<form action={action}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
Do it
</button>
{state.data && <p>Data {state.data}</p>}
</form>
);
}
For React version >=18
Starting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:
In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.
If not part of the framework, you can try some libs that implement it like swr.
Oversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.
let fullfilled = false;
let promise;
const fetchData = () => {
if (!fullfilled) {
if (!promise) {
promise = new Promise(async (resolve) => {
const res = await fetch('api/data')
const data = await res.json()
fullfilled = true
resolve(data)
});
}
throw promise
}
};
const Main = () => {
fetchData();
return <div>Loaded</div>;
};
const App = () => (
<Suspense fallback={"Loading..."}>
<Main />
</Suspense>
);
For React version <=17
I suggest to look at Dan Abramov (one of the React core maintainers) answer here:
I think you're making it more complicated than it needs to be.
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}
fetchMyAPI()
}, [])
return <div>{JSON.stringify(data)}</div>
}
Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like
const response = MyAPIResource.read();
and no effects. But in the meantime you can move the async stuff to a separate function and call it.
You can read more about experimental suspense here.
If you want to use functions outside with eslint.
function OutsideUsageExample({ userId }) {
const [data, dataSet] = useState<any>(null)
const fetchMyAPI = useCallback(async () => {
let response = await fetch('api/data/' + userId)
response = await response.json()
dataSet(response)
}, [userId]) // if userId changes, useEffect will run again
useEffect(() => {
fetchMyAPI()
}, [fetchMyAPI])
return (
<div>
<div>data: {JSON.stringify(data)}</div>
<div>
<button onClick={fetchMyAPI}>manual fetch</button>
</div>
</div>
)
}
Answer from ZiiMakc on Stack OverflowNote: If you want to support SSR/SSG for SEO, use framework specific api from React-router/Remix, Next.js or Gatsby.
For React version >=19
We now have nice loader and form action api:
Loading data:
function Comments({ dataPromise }) {
const data = use(dataPromise);
return <div>{JSON.stringify(data)}</div>;
}
async function loadData() {
return { data: "some data" };
}
export function Index() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments dataPromise={loadData()} />
</Suspense>
);
}
Form actions
async function updateDataAndLoadNew() {
// update data
// load new data
// return new data
return { data: "some data" };
}
export default function Index() {
const [state, action, isPending] = useActionState(
async (prev, formData) => {
return updateUserAndLoadNewData();
},
{ data: "initial state, no data" } // can be null
);
if (state.error) {
return `Error ${state.error.message}`;
}
return (
<form action={action}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
Do it
</button>
{state.data && <p>Data {state.data}</p>}
</form>
);
}
For React version >=18
Starting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:
In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.
If not part of the framework, you can try some libs that implement it like swr.
Oversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.
let fullfilled = false;
let promise;
const fetchData = () => {
if (!fullfilled) {
if (!promise) {
promise = new Promise(async (resolve) => {
const res = await fetch('api/data')
const data = await res.json()
fullfilled = true
resolve(data)
});
}
throw promise
}
};
const Main = () => {
fetchData();
return <div>Loaded</div>;
};
const App = () => (
<Suspense fallback={"Loading..."}>
<Main />
</Suspense>
);
For React version <=17
I suggest to look at Dan Abramov (one of the React core maintainers) answer here:
I think you're making it more complicated than it needs to be.
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}
fetchMyAPI()
}, [])
return <div>{JSON.stringify(data)}</div>
}
Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like
const response = MyAPIResource.read();
and no effects. But in the meantime you can move the async stuff to a separate function and call it.
You can read more about experimental suspense here.
If you want to use functions outside with eslint.
function OutsideUsageExample({ userId }) {
const [data, dataSet] = useState<any>(null)
const fetchMyAPI = useCallback(async () => {
let response = await fetch('api/data/' + userId)
response = await response.json()
dataSet(response)
}, [userId]) // if userId changes, useEffect will run again
useEffect(() => {
fetchMyAPI()
}, [fetchMyAPI])
return (
<div>
<div>data: {JSON.stringify(data)}</div>
<div>
<button onClick={fetchMyAPI}>manual fetch</button>
</div>
</div>
)
}
When you use an async function like
async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}
it returns a promise and useEffect doesn't expect the callback function to return Promise, rather it expects that nothing is returned or a function is returned.
As a workaround for the warning you can use a self invoking async function.
useEffect(() => {
(async function() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
})();
}, []);
or to make it more cleaner you could define a function and then call it
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
the second solution will make it easier to read and will help you write code to cancel previous requests if a new one is fired or save the latest request response in state
Working codesandbox
React useEffect hook and Async/await own fetch data func?
Setting React state from JSON data in useEffect
useEffect vs async/await
How to use async functions in useEffect (with examples)
Videos
Overall, you are heading in the right direction. For fetching data, you'd wanna use useEffect and pass [] as a second argument to make sure it fires only on initial mount.
I believe you could benefit from decoupling fetchJson function and making it more generic, as such:
const fetchJson = async (url) => {
const response = await fetch(url);
return response.json();
};
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchJson("https://api.coindesk.com/v1/bpi/currentprice.json")
.then(({ disclaimer }) => setData(disclaimer));
}, []);
return <div>{data}</div>;
};
Another option is to use a self invoking function:
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
let res = await fetch(
"https://api.coindesk.com/v1/bpi/currentprice.json" //example and simple data
);
let response = await res.json();
setData(response);
})();
}, []);
return <div>{data}</div>;
};
The suggestion to separate out the fetch logic to a separate function is a good idea and can be done as follows:
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
let response= await fetchData("https://api.coindesk.com/v1/bpi/currentprice.json");
setData(response);
})();
}, []);
return <div>{data}</div>;
};
const fetchData = async (url) => {
const response = await fetch(url);
const json = await response.json();
return json;
};
And yet another option is to create a wrapper function around useEffect that triggers the async function for you similar to this:
export function useAsyncEffect(effect: () => Promise<any>) {
useEffect(() => {
effect().catch(e => console.warn("useAsyncEffect error", e));
});
}
New to react.
So, I wrote some code and needed to refactor out to hooks. Couldn’t get the hook to work until I realized my
const useMyHook = async () => {
useEffect(()=>{
calling API stuff
}, [])
return {blah, blah, blah}
}had async wrapped around useEffect not inside. Vexed me for a few days. Sat down this morning and realized that pattern was maybe not correct. Took out that async and it works.
Forgive my potential syntax above.
So I’m trying to make sure I understand useEffect with this.
useEffect is not a replacement for async, but seems to resolve just fine without any async/await internals for an API call. Why is this and will I run into issues with longer wait times for responses?
Is best practice to use async functions inside of useEffect and then call those functions within the same useEffect with await then return? If not, what is the best practice?
I probably also need more info about useEffect. I find the docs kinda split between too low level technical and too high level.
My understanding so far is that it is a built in hook intended to be used when accessing external services. But I don’t really know why besides the convenience of monitoring some state and firing when it changes. Especially not sure why if you still have to implement async functions inside of it. Not sure what exactly useEffect provides.
There's a couple of problems with this snippet:
{videos.id.map((video) => {
<div key={video.id} {...videos}></div>
})}
In your code the
videosvariable is initiallynull, and then (after fetching) — an array. Arrays don't haveidproperty, and accessing properties ofnullis an illegal operation. You might want to first ensure that this is an array, rather thannull, and then also remove the ".id" part.You don't return anything from
.map()! Yes, you create a JSX element for each item of the array, but they are destroyed unused after that.
Consider using this instead:
{videos && videos.map((video) => (
<div key={video.id} {...videos}></div>
))}
Another problem is with variables' visibility:
import videos from "./path/to/data.json";
// and then later
const [ videos, setVideos ] = useState(null);
That first variable videos (the imported one) is not visible anymore, it got shadowed by the second one.
You either rename the second variable to prevent shadowing, or remove the first one completely as it is unused.
Next thing that I can see is that the code can't make up its mind about what it is actually trying to accomplish. On one hand, router provides an ID to a specific, particular video, which means that we're trying to show only one video. On the other hand though, the FeaturedVideo component is almost a perfect fit for showing the list of all the videos.
Judging from the names and overall setup though, it is somewhat clear that you're trying to show one video, not the whole list.
Looks like you're using react-router. If that's true, in FeaturedVideo you need to access the video ID, provided by router. Given that it is v5+, you can use useParams hook for that:
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router';
const [ video, setVideo ] = useState(null); // 'video', not 'videos'
const { videoID } = useParams();
useEffect(() => {
fetch("/src/data/data.json")
.then((res) => res.json())
.then((videos) => videos.filter((video) => {
return video.id === videoID;
}))
.then((matched) => setVideo(matched[0]));
}, []);
// Here `video` variable holds either `null` or the first matched array item.
// Render it using suggestions above.
References:
.map()method of arrays&&operator (see Description section)- Variable shadowing
- React Router Hooks
You are correct in using that useEffect hook. You want this data to load on component mount.
Assuming that URL returns JSON you can simply:
fetch(url)
.then(res => res.json().then(videos => setVideos(videos)));