npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-async-state-hook

v1.0.1

Published

React Async State provides a hook that allows you to set state using asynchronous functions both in server-side rendering and in the browser.

Downloads

2

Readme

React Async State Build Status

React Async State provides a hook that allows you to set state using asynchronous functions both in server-side rendering and in the browser.

Installation

npm i --save react-async-state-hook

Usage

React Async State provides the hook useAsyncState to replace the React hook useState when dealing with async data.

useAsyncState(initial, callback)

Where initial is the same as initial state parameter passed through to the useState hook, and callback is the async function that will be called immediately to set update the state.

Note that the value that is set on state is an object with the keys result, error and loading. If callback was successful then the data returned from it will be set on result. If callback was unsuccesful then the data on the error is caught and set on error. While waiting for callback to resolve loading is set to true, and at all other times is false.

Let's look at an example where callback is successful.

import useAsyncState from 'react-async-state';

const Welcome = () => {
  const [user, setUser] = useAsyncState(
    null,
    () => Promise.resolve('John'),
  );

  return (
    <>
      {user.loading && <span>Loading</span>}
      {user.result && <h1>Hi {user.result}</h1>}
      {user.error && <h3>Error: {user.error}</h3>}
    </>
  );
}

What we would expect in the above example that the Welcome component would render 'loading' until the promise was resolved at which point it would render 'Hi John'.

Let's look at a similar example, however this time an error is encountered.

import useAsyncState from 'react-async-state';

const Welcome = () => {
  const [user, setUser] = useAsyncState(
    null,
    () => Promise.reject(new Error('User does not exist')),
  );

  return (
    <>
      {user.loading && <span>Loading</span>}
      {user.result && <h1>Hi {user.result}</h1>}
      {user.error && <h3>Error: {user.error}</h3>}
    </>
  );
}

Now what we would expect is that the Welcome component would render 'loading' until the promise was reject at which point it would render 'Error: User does not exist'.

The above examples show how this can be used purely in the client. The advantage of the useAsyncState hook is the ability to use it on the server where it can resolve callback during server side rendering. This means that the response from the server to the client will have the exact markup that the client would have after all async methods have resolved. Additionally, the state for all these components will be loaded correctly without running callback again on the client provided that you have properly configured your response (details below).

Let's assume we have a node web server set up with a basic function to handle the request looks something like below, where bundle.js contains your bundled javascript that contains React code that binds to <div id="app"></div>.

(req, res) => {
  res.send(`
    <!doctype html>
    <html>
      <head>
        <script src="/static/bundle.js"></script>
      </head>
      <body>
        <div id="app"></div>
      </body>
    </html>
  `)
}

Let's assume that in bundle.js we are calling the following code:

ReactDOM.render(<Welcome/>, document.getElementById('app'));

Traditionally if we were going to use server side rendering we'd use hydrate over render:

ReactDOM.hydrate(<Welcome/>, document.getElementById('app'));

And also update the way we handle the response:

(req, res) => {
  const html = ReactDOMServer.renderToString(<Welcome/>);
  
  res.send(`
    <!doctype html>
    <html>
      <head>
        <script src="/static/bundle.js"></script>
      </head>
      <body>
        <div id="app">${html}</div>
      </body>
    </html>
  `)
}

Even with our Welcome component calling useAsyncState it will first set the set the user.result to the default value provided (which is null in this case) and nothing will be rendered within the div we are mounting to as html will be an empty string. Once the browser runs the javascript the same process described above will take place, with the callback function being resolved and the value of user being updated re-rendering the component.

So just using useAsyncState in our components by itself doesn't allow us to properly render what the user would see after the initial mount. What would be ideal is if we could not only have html contain the markup that the user would see after the initial mount, but if we could also set the value of user on the Welcome component to be that which was calculated on the server during server side rendering.

What we can do is modify the function on the server to tell useAsyncState that we are on the server and therefore we want to wait until the callback function resolves before rendering. We also want to collect the internal state of the component and pass that through to the client so that it can load that state into the component when it is created. The way that we do this is by calling createStore and pass the store base64 encoded via a dataset attribute on <body>:

import { createStore, encodeStore } from 'react-async-state';

(req, res) => {
  const store = createStore();
  const html = ReactDOMServer.renderToString(<Welcome/>);
  
  res.send(`
    <!doctype html>
    <html>
      <head>
        <script src="/static/bundle.js"></script>
      </head>
      <body data-state="${encodeStore(store)}">
        <div id="app">${html}</div>
      </body>
    </html>
  `)
}

The function createStore takes a parameter isServer that defaults to true, so in the above example we are creating a new store that we've said is on the server. This tells useAsyncState to wait for the callback it is given to resolve before rendering. After all the components have been rendered via ReactDOM.renderToString the store now contains the data we can send to the client so that it can load it into the components when they are first created. The way we send this data is through the data-state attribute on the body tag, as this is where the components will look up their state after decoding the store. What this means means is that any component using useAsyncState which was rendered on the server will not call their callback function to obtain state, rather simply take it from the store.

Notes

This package uses deasync which is a controversial package to use in production environments.

There are plenty of articles out there that provide extensive reasons not to use it, for example this article covers quite a bit of detail.

Use react-async-state in production environments at your own risk. It is only being used here as a quick way to solve a complex problem.