DEV Community

Cover image for Recoil - The Asynchronous way to manage state [ Part 1 ]
Shubham Kamath
Shubham Kamath

Posted on

Recoil - The Asynchronous way to manage state [ Part 1 ]

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.

Alt Text

Recoil asynchronous state management supports -

  1. React <Suspense/>. The fallback UI is rendered till the pending request is completed.
  2. 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 with fetch .
  • We set an async function to get 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 the fetchUserDetails 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 while fallback 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 to fetchUserDetails Selector.

  • Further, useRecoilValueLoadable returns an object with state 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 to hasValue, the object returned by useRecoilValueLoadable holds the data that was pending in contents 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.

GitHub logo 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)

Collapse
 
vinaydevs profile image
Vinay Dev S

Thank You ❤️

Collapse
 
deepaksinghkushwah profile image
Deepak Singh Kushwah

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.