In previous post we got an overview of some Recoil terminologies and how to manage state using it.
Here, we will explore Asynchronous side of Recoil.
Recoil asynchronous state management supports -
- React
<Suspense/>
. The fallback UI is rendered till the pending request is completed. - Without React
<Suspense/>
, status of data from Recoil hooks can be used to check if it's still loading or completed or have caught an error.
Let's create an app that would fetch and display data from an API
To start with, Create a new create-react-app
and clean it for a new project and wrap <RecoilRoot/>
around the root.
1. Let's start with writing a Selector
which will fetch data.
import React from 'react';
import { selector } from 'recoil';
const url = `https://reqres.in/api/users?page=1`;
const fetchUserDetails = selector({
key: 'userDetailsSelector',
get: async ({ get }) => {
try{
const response = await fetch(url);
const data = await response.json();
return data;
}catch(error){
throw error;
}
}
});
function App() {
return (
<div>
<p> Recoil Example </p>
</div>
);
}
export default App;
- Using
selector
we fetch data withfetch
. - We set an
async
function toget
parameter which will return the fetched data. - We can use value from
atom
to set URL parameter or body data like user id, page number and auth key but we skip it this time.
2. We create a component called <DetailsWithSuspense/>
which would subscribe to fetchUserDetails Selector
and render data.
import React from 'react';
import { selector, useRecoilValue } from 'recoil';
const url = `https://reqres.in/api/users?page=1`;
const fetchUserDetails = selector({
key: 'userDetailsSelector',
get: async ({ get }) => {
try{
const response = await fetch(url);
const data = await response.json();
return data;
}catch(error){
throw error;
}
}
});
const DetailsWithSuspense = () => {
const userDetails = useRecoilValue(fetchUserDetails);
const { data } = userDetails;
return (
data.map(item => (
<div key={item.id}>
<p>
{`Email: ${item.email} Name: ${item.first_name} ${item.last_name}`}.
</p>
</div>
))
);
}
function App() {
return (
<div>
<p> Recoil Example </p>
</div>
);
}
export default App;
- Here we use
useRecoilValue
hook to subscribe and get the value of thefetchUserDetails Selector
. - But, we can also use
useRecoilState
hook to get the value and a function to set the value. ( Here, we can't set the value as data returned by selector is Read Only )
3. Further, Let's add <Suspense/>
to render asynchronous data
import React from 'react';
import { selector, useRecoilValue } from 'recoil';
const url = `https://reqres.in/api/users?page=1`;
const fetchUserDetails = selector({
key: 'userDetailsSelector',
get: async ({ get }) => {
try{
const response = await fetch(url);
const data = await response.json();
return data;
}catch(error){
throw error;
}
}
});
const DetailsWithSuspense = () => {
const userDetails = useRecoilValue(fetchUserDetails);
const { data } = userDetails;
return (
data.map(item => (
<div key={item.id}>
<p>
{`Email: ${item.email} Name: ${item.first_name} ${item.last_name}`}.
</p>
</div>
))
);
}
function App() {
return (
<div>
<React.Suspense fallback={<div>Loading...</div>}>
<DetailsWithSuspense />
</React.Suspense>
</div>
);
}
export default App;
We wrap the
<DetailsWithSuspense />
with<Suspense/>
which takes care of pending data whilefallback
component is rendered till the asynchronous call is completed or have errors.To create an Error Handling Component, refer to Error Boundaries.
If <Suspense/>
is not your way, Recoil still got your back! 👇
4. We create and add another component called <DetailsWithoutSuspense />
which would subscribe to fetchUserDetails Selector
and render data.
import React from 'react';
import { selector, useRecoilValue, useRecoilValueLoadable } from 'recoil';
const url = `https://reqres.in/api/users?page=1`;
const fetchUserDetails = selector({
key: 'userDetailsSelector',
get: async ({ get }) => {
try{
const response = await fetch(url);
const data = await response.json();
return data;
}catch(error){
throw error;
}
}
});
const DetailsWithoutSuspense = () => {
const userDetails = useRecoilValueLoadable(fetchUserDetails);
const { state } = userDetails;
if (userDetails.state === 'hasError') {
return <div> There is some problem! </div>
}
if(state === 'loading'){
return <div>Its loading</div>
}
if(state === 'hasValue'){
const { contents: { data }} = userDetails;
return (
data.map(item => (
<div key={item.id}>
<p>
{`Email: ${item.email} Name: ${item.first_name} ${item.last_name}`}.
</p>
</div>
))
);
}
}
const DetailsWithSuspense = () => {
const userDetails = useRecoilValue(fetchUserDetails);
const { data } = userDetails;
return (
data.map(item => (
<div key={item.id}>
<p>
{`Email: ${item.email} Name: ${item.first_name} ${item.last_name}`}.
</p>
</div>
))
);
}
function App() {
return (
<div>
<DetailsWithoutSuspense />
<React.Suspense fallback={<div>Loading...</div>}>
<DetailsWithSuspense />
</React.Suspense>
</div>
);
}
export default App;
We use
useRecoilValueLoadable
hook to subscribe tofetchUserDetails Selector
.-
Further,
useRecoilValueLoadable
returns an object withstate
key, which holds current status of pending data which can be either -a.
hasError
: set when an error occurs
b.loading
: set when data is pending
c.hasValue
: set when data is received successfully Depending on
state
value, a component can be rendered accordingly.When
state
value is set tohasValue
, the object returned byuseRecoilValueLoadable
holds the data that was pending incontents
key.
This would complete the little Fetch Data app which gets data asynchronously using Recoil APIs. If you want to see a more structured approach, checkout GitHub repository below.
shubhaemk / recoil-async-example
Trying the asynchronous side of Recoil
Next I will be exploring selectorFamily
which is similar to selector
but accepts a parameter.
At the end, I would like to thanks Reqres for giving APIs to test.
Top comments (2)
Thank You ❤️
Thank you for posting this tutorial series. It help me a lot. I am new to React ecosystem and Redux is overhead for me. I was searching for exactly this information.