Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
January 7, 2021 10:52 am GMT

React State Management with Recoil

Recoil is a state management library for React. It is still in experimental phase, but it looks really promising. The best thing about Recoil is that it works and thinks like React. The most important concepts of Recoil are atoms and selectors.

Atoms are units of state, while selectors are pure functions that calculate derived data from state. Selectors accept both atoms and other selectors as input. Components can subscribe to selectors or atoms, and will be re-rendered when the selectors or atoms change.

recoil

Created with Sketchpad

I will explain how Recoil can manage your applications state through some examples. No, it wont be another todo app. Our app will show a list of songs, and for each song we can get some extra info. I will share the GitHub repository at the end of the article.

First of all, we need to create a new React app:

npx create-react-app recoil-examplecd recoil-exampleyarnyarn start
Enter fullscreen mode Exit fullscreen mode

Check that your app works on localhost:3000, you should see a page like this:

recoil

Then we need to add Recoil to our app:

yarn add recoil
Enter fullscreen mode Exit fullscreen mode

We need to wrap our components that use Recoil in RecoilRoot. We can replace the content of App.js with:

// App.jsimport React from 'react';import { RecoilRoot } from 'recoil';import './App.css';const App = () => (  <div className={'App'}>    <RecoilRoot>      <h1>Recoil Example</h1>    </RecoilRoot>  </div>);export default App;
Enter fullscreen mode Exit fullscreen mode

Our app should still work and show the changes we made:

recoil

We will create a real-world-like example, so we will start with our client:

// client.jsconst songList = [  { id: 1, title: 'Bohemian Rhapsody' },  { id: 2, title: 'Purple Rain' },  { id: 3, title: 'One' },  { id: 4, title: 'Eternal Flame' },];const songDetails = [  { id: 1, artist: 'Queen', year: 1975 },  { id: 2, artist: 'Prince', year: 1984 },  { id: 3, artist: 'U2', year: 1992 },  { id: 4, artist: 'The Bangles', year: 1989 },];export const getSongs = async () =>  new Promise(resolve => setTimeout(() => resolve(songList), 500));export const getSongById = async id =>  new Promise(resolve => {    const details = songDetails.find(s => s.id === id);    return setTimeout(() => resolve(details), 500);  });
Enter fullscreen mode Exit fullscreen mode

Now that we have our client functions, we can implement the atoms and selectors that will manage our apps state. Each atom and selector will have a unique id. We will start with loading the song list. As our client function returns a promise, the selectors get function will be async:

// selectors.jsimport { selector } from 'recoil';import { getSongs } from './client';export const songsQuery = selector({  key: 'songs',  get: async () => {    const response = await getSongs();    return response;  },});
Enter fullscreen mode Exit fullscreen mode

Now that we have our client functions, we can implement the atoms and selectors that will manage our apps state. Each atom and selector will have a unique id. We will start with loading the song list. As our client function returns a promise, the selectors get function will be async:

  • useRecoilState returns the value of the given state and the setter function for updating the value of given the state;
  • useRecoilValue returns the value of the given state;
  • useSetRecoilState returns the setter function for updating the value of given the state.

We will create the Songs component:

// Songs.jsimport React from 'react';import { useRecoilValue, useSetRecoilState } from 'recoil';import { songsQuery } from './selectors';import { currentSongIDState } from './atoms';const Songs = () => {  const songs = useRecoilValue(songsQuery);  const setCurrentSongID = useSetRecoilState(currentSongIDState);  return (    <>      <h2>Songs</h2>      {songs.map(song => (        <div key={song.id}>          <p onClick={() => setCurrentSongID(song.id)}>{song.title}</p>        </div>      ))}    </>  );};export default Songs;
Enter fullscreen mode Exit fullscreen mode

We should note that our selector is async, but React render functions are synchronous. Here comes in React Suspense, which handles pending data. We could also handle pending state with Recoils Loadable, or implement a handler from scratch, but we will use Suspense now:

// App.jsimport React, { Suspense } from 'react';import { RecoilRoot } from 'recoil';import Songs from './Songs';import './App.css';const App = () => (  <div className={'App'}>    <RecoilRoot>      <Suspense fallback={<span>Loading...</span>}>        <Songs />      </Suspense>    </RecoilRoot>  </div>);export default App;
Enter fullscreen mode Exit fullscreen mode

Now in our browser we should see the list of songs:

recoil

That was easy, right?

Now lets see how can we get the details of a song. When we select a song, we want to see its details, like the artist and the year of release. We need to remember the current song ID. The ID is just a simple value, it will not be computed, so we will create an atom for this, instead of a selector:

// atoms.jsimport { atom } from 'recoil';export const currentSongIDState = atom({  key: 'currentSongID',  default: '',});
Enter fullscreen mode Exit fullscreen mode

Based on the current song ID we want to get the song details. We need another selector which calls the client function with the current song ID. Selectors can read other atoms and selectors using the get argument of the get function. I know it sounds a little confusing, but the next example will make it more clear:


// selectors.jsimport { selector } from 'recoil';import { currentSongIDState } from './atoms';import { getSongs, getSongById } from './client';// ...export const currentSongQuery = selector({  key: 'currentSong',  get: async ({ get }) => {    const response = await getSongById(get(currentSongIDState));    return response;  },});
Enter fullscreen mode Exit fullscreen mode

We will now create the CurrentSong component, which renders the details of the selected song:

// CurrentSong.jsimport React from 'react';import { useRecoilValue } from 'recoil';import { currentSongQuery } from './selectors';const CurrentSong = () => {  const currentSong = useRecoilValue(currentSongQuery);  return currentSong ? (    <>      <h2>Current Song Details:</h2>      <p>Artist: {currentSong.artist}</p>      <p>Released: {currentSong.year}</p>    </>  ) : null;};export default CurrentSong;
Enter fullscreen mode Exit fullscreen mode

Then we can add it to our Songs component. The currentSongIDState atom can be updated from the component by using the setter function returned by useRecoilState. (Note that I didnt want to add it to the App component, because I dont want to show the Loading state when nothing is selected. Of course, we could structure our app better, but for now its just fine):

// Songs.jsimport React, { Suspense } from 'react';import { useRecoilValue, useRecoilState } from 'recoil';import { songsQuery } from './selectors';import { currentSongIDState } from './atoms';import CurrentSong from './CurrentSong';const Songs = () => {  const songs = useRecoilValue(songsQuery);  const [currentSongID, setCurrentSongID] = useRecoilState(currentSongIDState);  /*   * as an alternative, we could declare them separately:   * const currentSongID = useRecoilValue(currentSongIDState);   * const setCurrentSongID = useSetRecoilState(currentSongIDState);   */  return (    <>      <h2>Songs</h2>      {songs.map(song => (        <div key={song.id}>          <p onClick={() => setCurrentSongID(song.id)}>            {song.title} {song.id === currentSongID && '*'}          </p>        </div>      ))}      {currentSongID && (        <Suspense fallback={<span>Loading...</span>}>          <CurrentSong />        </Suspense>      )}    </>  );};export default Songs;
Enter fullscreen mode Exit fullscreen mode

If we click on a song we should see the details below the song list:

recoil

It was easy and fun so far, while working with read-only data, but in real-world apps we want our apps state to get updated after doing an update on the server. For example, we might want to add new songs to our list. Here it becomes a little more complicated.

If you are used to work with other state management libraries, like Redux, then you know that the global state can be updated after updating the data on the server. Recoil does not have a global state, like other state management libraries, but coupled to RecoilRoot. That means the state can not be updated outside of the components/hooks.

But there is still hope... with Recoil we can achieve this by subscribing to server updates from useEffect, and updating the state from there. I know this is not ideal, but this API is still under development, and Recoil might handle this out-of-the-box.

In conclusion, comparing it with other state management libraries (like Redux), it seems more React-like and simpler and easier to learn, so it might be a good alternative in the future.

You can find the GitHub repository here. Thank you for reading this article.


Original Link: https://dev.to/kingahunyadi/react-state-management-with-recoil-5a9k

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To