0.1.74 • Published 3 months ago

react-imperative-hook v0.1.74

Weekly downloads
-
License
MIT
Repository
github
Last release
3 months ago

react-imperative-hook

The declarative nature of React is great for most use cases, but not always. When working with async UI flows like modals, toasts and drawers, it's often preferable to have an imperative and promise based interface, which is what this library provides.

Heads up: This package does not follow semantic versioning. Changes of all types are released to the patch portion of the version string.

Features

  • Designed for modals and toasts, but not limited to.
  • Lightweight with zero runtime dependencies.
  • Written in TypeScript with proper generics in mind.

Try it on StackBlitz

Install

npm install react-imperative-hook

Usage

1. Define your imperative primitives

Use the createImperative factory to define the primitives that you will be using. The factory is useful to be able to customize rendering, and it allows you to produce multiple instances, i.e. if you want separate rendering for modals and toasts.

See the Primitives section for more information on each primitive.

// imperative.ts

import {
  createImperative,
  ImperativeComponentProps,
} from "react-imperative-hook";

// The default primitive names are abstract, so it's
// recommended to give them a more concrete name for your use-case.
const {
  Outlet: ModalOutlet,
  Context: ModalContext,
  usePredefinedSpawner: useModal,
  useInlineSpawner: useModals,
  useSpawnSustainer: useModalSustainer,
} = createImperative();

// For TypeScript, use this type helper to define a props type
// for components that should be compatible with the imperative hooks.
// T is the type of the value the component resolves with.
export type ModalProps<T = void> = ImperativeComponentProps<T>;

2. Create a compatible react component

Any component that accept ImperativeComponentProps is compatible with the hooks.

// Prompt.tsx

import { ReactNode, useState } from "react";
import { ModalProps } from "./imperative";

function Prompt({
  resolve,
  message,
}: ModalProps<string> & { message: ReactNode }) {
  const [input, setInput] = useState("");
  return (
    <dialog open>
      <p>{message}</p>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={() => resolve(input)}>Submit</button>
    </dialog>
  );
}

3. Place the outlet at the root of your app

// main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ModalOutlet } from "./imperative";
import { App } from "./App";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
    <ModalOutlet />
  </StrictMode>,
);

4. Use the hooks in your app

// App.tsx

import { useModal, useModals } from "./imperative";

export function App() {
  const prompt = useModal(Prompt, { message: "Default message" });
  const showModal = useModals();

  async function promptManyTimes() {
    // Will display a prompt dialog containing the message "Default message"
    const promise = prompt();

    // The function returns a promise that resolves when the `resolve` function
    // is called from within the component, and will resolve with the value that was passed.
    const input = await promise;

    // You can override default props by providing specific props per spawn
    await prompt({
      message: `This was your previous input: "${input}"`,
    });

    await showModal(Prompt, {
      message:
        "Use can use the inline spawner hook to specify a component later",
    });
  }

  return <button onClick={promptManyTimes}>Start sequence</button>;
}

Primitives

See StackBlitz for examples for all primitives.

Outlet

A React component that renders the currently spawned components.

Context

A React context that holds the store that manages all the state of react-imperative-hook. Using this is optional. The context already contain a default store, and the hooks are using the store internally. Manual use of the context and store is only necessary if you want to extend the library with custom behavior.

usePredefinedSpawner

A hook that takes a component that you specify ahead of time and returns a function that will spawn the given component when called.

  • Useful when you want to spawn the same component often.
  • Allows you to specify default props ahead of time that when updated will propagate to any spawned components.

useInlineSpawner

A hook that just returns a function that will spawn a component when called. The component is however specified inline when you call the function.

  • This is useful when you want to spawn different components, or don't know what component to spawn until later.
  • Comes with the caveat that you cannot update default props once the component has been spawned

useSpawnSustainer

A hook that is designed to be used from within a component you spawn with useInlineSpawner or usePredefinedSpawner. Once used it will prevent the spawned component from being removed from the store, keeping it in the DOM. This is useful to allow animations to finish before removing the component. Once you no longer want to sustain the component, call the function returned from the hook.

createImperative

Produces the primitives in this list. Allows you to pass in a React component to customize how all spawned components should be rendered in the outlet.

0.1.74

3 months ago

0.1.72

3 months ago

0.1.73

3 months ago

0.1.52

3 months ago

0.1.53

3 months ago

0.1.54

3 months ago

0.1.55

3 months ago

0.1.56

3 months ago

0.1.57

3 months ago

0.1.58

3 months ago

0.1.59

3 months ago

0.1.50

3 months ago

0.1.51

3 months ago

0.1.49

3 months ago

0.1.41

3 months ago

0.1.42

3 months ago

0.1.43

3 months ago

0.1.44

3 months ago

0.1.45

3 months ago

0.1.46

3 months ago

0.1.47

3 months ago

0.1.48

3 months ago

0.1.40

3 months ago

0.1.38

3 months ago

0.1.39

3 months ago

0.1.30

3 months ago

0.1.31

3 months ago

0.1.32

3 months ago

0.1.33

3 months ago

0.1.34

3 months ago

0.1.35

3 months ago

0.1.36

3 months ago

0.1.37

3 months ago

0.1.70

3 months ago

0.1.71

3 months ago

0.1.29

3 months ago

0.1.63

3 months ago

0.1.64

3 months ago

0.1.65

3 months ago

0.1.66

3 months ago

0.1.67

3 months ago

0.1.68

3 months ago

0.1.69

3 months ago

0.1.60

3 months ago

0.1.61

3 months ago

0.1.62

3 months ago

0.1.12

3 months ago

0.1.13

3 months ago

0.1.14

3 months ago

0.1.15

3 months ago

0.1.27

3 months ago

0.1.28

3 months ago

0.1.20

3 months ago

0.1.21

3 months ago

0.1.22

3 months ago

0.1.23

3 months ago

0.1.24

3 months ago

0.1.25

3 months ago

0.1.26

3 months ago

0.1.16

3 months ago

0.1.17

3 months ago

0.1.18

3 months ago

0.1.19

3 months ago

0.1.11

4 months ago

0.1.10

8 months ago

0.1.9

9 months ago

0.1.8

9 months ago

0.1.7

10 months ago

0.1.6

10 months ago

0.1.5

10 months ago

0.1.4

10 months ago

0.1.3

10 months ago

0.1.2

10 months ago

0.1.1

10 months ago

0.1.0

10 months ago

0.0.4

10 months ago

0.0.3

10 months ago

0.0.2

10 months ago

0.0.1

10 months ago

0.0.0

10 months ago