GithubHelp home page GithubHelp logo

f / react-wait Goto Github PK

View Code? Open in Web Editor NEW
305.0 4.0 30.0 2.53 MB

Complex Loader Management Hook for React Applications

Home Page: https://codesandbox.io/s/y3w5v5lk0j

License: MIT License

JavaScript 100.00%
react hooks react-hooks loading ui-components ux

react-wait's Introduction

Complex Loader Management Hook for React.

Read the Medium post "Managing Complex Waiting Experiences on Web UIs".

npm version build codecov


Edit useWait

react-wait is a React Hook helps to manage multiple loading states on the page without any conflict. It's based on a very simple idea that manages an Array of multiple loading states. The built-in loader component listens its registered loader and immediately become loading state.

Why not React.Suspense?:

React has its own Suspense feature to manage all the async works. For now it only supports code-splitting (not data-fetching).

useWait allows you to manage waiting experiences much more explicitly and not only for Promised/async patterns but also complete loading management.

Overview

Here's a quick overview that what's useWait for:

import { useWait, Waiter } from "react-wait";

function A() {
  const { isWaiting } = useWait();
  return (
    <div>
      {isWaiting("creating user") ? "Creating User..." : "Nothing happens"}
    </div>
  );
}

function B() {
  const { anyWaiting } = useWait();
  return (
    <div>
      {anyWaiting() ? "Something happening on app..." : "Nothing happens"}
    </div>
  );
}

function C() {
  const { startWaiting, endWaiting, isWaiting } = useWait();

  function createUser() {
    startWaiting("creating user");
    // Faking the async work:
    setTimeout(() => {
      endWaiting("creating user");
    }, 1000);
  }

  return (
    <button disabled={isWaiting("creating user")} onClick={createUser}>
      <Wait on="creating user" fallback={<Spinner />}>
        Create User
      </Wait>
    </button>
  );
}

ReactDOM.render(
  <Waiter>
    <C />
  </Waiter>,
  document.getElementById("root")
);

Quick Start

If you are a try and learn developer, you can start trying the react-wait now using codesandbox.io.

Quick start on CodeSandbox

1. Install:

yarn add react-wait

2. Require:

import { Waiter, useWait } from "react-wait";

function UserCreateButton() {
  const { startWaiting, endWaiting, isWaiting, Wait } = useWait();

  return (
    <button
      onClick={() => startWaiting("creating user")}
      disabled={isWaiting("creating user")}
    >
      <Wait on="creating user" fallback={<div>Creating user!</div>}>
        Create User
      </Wait>
    </button>
  );
}

3. Wrap with the Waiter Context Provider

And you should wrap your App with Waiter component. It's actually a Context.Provider that provides a loading context to the component tree.

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Waiter>
    <App />
  </Waiter>,
  rootElement
);

Installation

$ yarn add react-wait
# or if you using npm
$ npm install react-wait

The API

react-wait provides some helpers to you to use in your templates.

anyWaiting()

Returns boolean value if any loader exists in context.

const { anyWaiting } = useWait();

return <button disabled={anyWaiting()}>Disabled while waiting</button>;

isWaiting(waiter String)

Returns boolean value if given loader exists in context.

const { isWaiting } = useWait();

return (
  <button disabled={isWaiting("creating user")}>
    Disabled while creating user
  </button>
);

startWaiting(waiter String)

Starts the given waiter.

const { startWaiting } = useWait();

return <button onClick={() => startWaiting("message")}>Start</button>;

endWaiting(waiter String)

Stops the given waiter.

const { end } = useWait();

return <button onClick={() => endWaiting("message")}>Stop</button>;

Using Wait Component

function Component() {
  const { Wait } = useWait();
  return (
    <Wait on="the waiting message" fallback={<div>Waiting...</div>}>
      The content after waiting done
    </Wait>
  );
}

Better example for a button with loading state:

<button disabled={isWaiting("creating user")}>
  <Wait on="creating user" fallback={<div>Creating User...</div>}>
    Create User
  </Wait>
</button>

Making Reusable Loader Components

With reusable loader components, you will be able to use custom loader components as example below. This will allow you to create better user loading experience.

function Spinner() {
  return <img src="spinner.gif" />;
}

Now you can use your spinner everywhere using waiting attribute:

<button disabled={isWaiting("creating user")}>
  <Wait on="creating user" fallback={<Spinner />}>
    Create User
  </Wait>
</button>

Creating Waiting Contexts using createWaitingContext(context String)

To keep your code DRY you can create a Waiting Context using createWaitingContext.

function CreateUserButton() {
  const { createWaitingContext } = useWait();

  // All methods will be curried with "creating user" on.
  const { startWaiting, endWaiting, isWaiting, Wait } = createWaitingContext(
    "creating user"
  );

  function createUser() {
    startWaiting();
    setTimeout(endWaiting, 1000);
  }

  return (
    <Button disabled={isWaiting()} onClick={createUser}>
      <Wait fallback="Creating User...">Create User</Wait>
    </Button>
  );
}

Contributors

  • Fatih Kadir Akın, (creator)

Other Implementations

Since react-wait based on a very simple idea, it can be implemented on other frameworks.

  • vue-wait: Multiple Process Loader Management for Vue.
  • dom-wait: Multiple Process Loader Management for vanilla JavaScript.

License

MIT © Fatih Kadir Akın

react-wait's People

Contributors

cemremengu avatar dimorphic avatar f avatar rodrigonehring avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

react-wait's Issues

Add withWait() HOC

Thank you for this convenient library.

Is it possible to implement a withWait() HOC to allow class components to use react-wait ?

startWaiting with useEffect hook overrides waiters array

Hi there!
Thank you for this awesome library.
I experienced a kind of weird behavior while working with this library.

Description
if I use method startWaiting in useEffect hook like

useEffect(() => {
   startWaiting('first');
   startWaiting('second');
}, []); 

Result
waiters array contains only 'second' waiter, so waiters array is overwritten.

the same issue would be in the example below:

component1:

const Component1 = () => {

 useEffect(() => {
     const fetchData = async () => {
         startWaiting('first');
         const response = await apiCall();
         endWaiting('first');
     }
     fetchData();
 }, []);

return (/*react markup and Wait component inside*/);

component2:

const Component2 = () => {

 useEffect(() => {
     const fetchData = async () => {
         startWaiting('second');
         const response = await anotherApiCall();
         endWaiting('second');
     }
     fetchData(); 
 }, []);

return (/*react markup and Wait component inside*/);

component3:

const Component3 = () => 
(
<Waiter>
   <Component1 />
   <Component2 />
</Waiter>
);

in this case, every time I am trying to receive waiters array it will contain only 'second'.

is it compatible with nextjs and server rendering?

Hello could you tell me if it compatible with nextjs and server rendering?

I'm getting an error n.useState is not a function

TypeError: n.useState is not a function
at exports.Waiter (/app/node_modules/react-wait/dist/react-wait.js:23:13)
at processChild (/app/node_modules/react-dom/cjs/react-dom-server.node.development.js:2475:14)
at resolve (/app/node_modules/react-dom/cjs/react-dom-server.node.development.js:2401:5)
at ReactDOMServerRenderer.render (/app/node_modules/react-dom/cjs/react-dom-server.node.development.js:2728:22)
at ReactDOMServerRenderer.read (/app/node_modules/react-dom/cjs/react-dom-server.node.development.js:2699:25)
at renderToString (/app/node_modules/react-dom/cjs/react-dom-server.node.development.js:3113:25)
at renderPage (/app/node_modules/next/dist/server/render.js:319:26)
at Function.getInitialProps (/app/node_modules/next/dist/server/document.js:65:25)
at _callee$ (/app/node_modules/next/dist/lib/utils.js:86:30)
at tryCatch (/app/node_modules/@babel/runtime-corejs2/node_modules/regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/app/node_modules/@babel/runtime-corejs2/node_modules/regenerator-runtime/runtime.js:288:22)
at Generator.prototype.(anonymous function) [as next] (/app/node_modules/@babel/runtime-corejs2/node_modules/regenerator-runtime/runtime.js:114:21)
at asyncGeneratorStep (/app/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:5:24)
at _next (/app/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:27:9)
at /app/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:34:7
at new Promise ()

I'm using nextjs version 7.0.2

or something different can cause this error?

is it compatible with Suspense?

Hi, the React team has launced Suspense for data loading and is about to release it for concurrent updates. Is this library using that or is this is a custom solution?

Exposing the current Waiters

Hey!

This lib seems awesome, but there's one api function that's missing for us before we decide to use it. Our usecase is that we have multiple components with separate datasources that we need to keep track of the loading state for. We're playing around with the idea of having a progress bar, but for that we'd need to keep track of all the current waiters. anyWaiters gets us far, but we'd need the actual length to make something useful happen in terms of loading bars.

What do you think, is this a decent idea or does it not belong in this repo?

Waiters composition

I've been using the library for a while and I noticed that I could simplify my code with something like composing of the waiters. For example, given that I have two waiters bar and baz, I would like to have foo waiter that is waiting if one of bar or baz is waiting.

Motivation

Now, first I was thinking about adding array argument support to isWaiting so that I could call isWaiting(["foo", "bar"]) or a bit more explicit convenience function isAnyWaiting(["foo", "bar"]).

But after looking into my code I realized it's not enough. In my case, I have some big component
Foo rendering smaller components Bar and Baz that wait on each other. That works fine with current API. It gets more convoluted when you would like to have some other component Qux that doesn't exactly want to know about the lower-level ones but needs to know if Foo or any of it's children waits. So currently in Qux you would need to write

const isFooWaiting = isWaiting("baz") && isWaiting("baz")

That's not ideal because Qux needs to know too much about Foo internals.

Proposal

Create a separate method that will create a new waiter if one of the child waiters is waiting. It could be similar in usage to createWaitingContext:

const { compose } = useWait();
const { isWaiting } = compose("Foo", ["Bar", "Baz"])
  • Foo is a waiter, you can check if it's waiting with isWaiting("Foo")
  • Foo is always waiting if Bar or Baz is waiting.
  • the open question is should you be able to manually start and end a composed waiter?

Anternative idea

Maybe instead adding a compose method, we could compose Waiter providers? It's just an idea, I don't know if it's possible or makes sense but provier could have an optional name and be a child of another warapper like this?

<Waiter>
  <Waiter name="foo">
    // ...
  </Waiter>
</Waiter>

Then if the inner's anyWaiting() is true, foo is waiting in the outer waiter?
Again, it's just an idea.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.