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 OverflowNew 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.
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>
)
}
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
Videos
Create an async function inside your effect that wait the getData(1) result then call setData():
useEffect(() => {
const fetchData = async () => {
const data = await getData(1);
setData(data);
}
fetchData();
}, []);
If you're invoking it right-away you might want to use it as an anonymous function:
useEffect(() => {
(async () => {
const data = await getData(1);
setData(data);
})();
}, []);