Gordon  Matlala

Gordon Matlala

1679389560

React-wait: Complex Loader Management Hook for React Applications

React-wait

Complex Loader Management Hook for React.

use-wait.gif 


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>
  );
}

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


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.

Download Details:

Author: F
Source Code: https://github.com/f/react-wait 
License: MIT license

#react #hooks #loading 

React-wait: Complex Loader Management Hook for React Applications
Lawrence  Lesch

Lawrence Lesch

1677099120

Vue-composable: Vue Composition-api Composable Components

Vue-composable

Out-of-the-box ready to use composables

  • 🌴 TreeShakable
  • 🧙‍♂️ Fully Typescript
  • 💚 Vue 3 and 2 support
  • 🔨 Vue Devtools support

Introduction

This library aim is to be one stop shop for many real-world composable functions, with aggressive tree-shaking to keep it light on your end code.

Installing

# @vue/composition-api

# install with yarn
yarn add @vue/composition-api vue-composable

# install with npm
npm install @vue/composition-api vue-composable

# vue 3

# install with yarn
yarn add vue-composable

# install with npm
npm install vue-composable

Install plugin

To use devtools you need to install the plugin first:

import { createApp } from "vue";
import { VueComposableDevtools } from "vue-composable";
import App from "./App.vue";

const app = createApp(App);
app.use(VueComposableDevtools);
// or
app.use(VueComposableDevtools, {
  id: "vue-composable",
  label: "devtool composables"
});

app.mount("#app");

Component State

To add properties to the component inspector tab ComponentState

const bar = "bar";
useDevtoolsComponentState(
  {
    bar
  },
  {
    type: "custom composable" // change group
  }
);

const baz = () => "baz";
useDevtoolsComponentState({ baz });
// no duplicates added by default
useDevtoolsComponentState({ baz });

const the = 42;
useDevtoolsComponentState({ the });
// to allow multiple same key
useDevtoolsComponentState({ the }, { duplicate: true });

// use a devtools api list directly
interface StateBase {
  key: string;
  value: any;
  editable: boolean;
  objectType?: "ref" | "reactive" | "computed" | "other";
  raw?: string;
  type?: string;
}
useDevtoolsComponentState([
  {
    key: "_bar",
    type: "direct",
    value: "bar",
    editable: true
  },
  {
    key: "_baz",
    type: "direct",
    value: "baz",
    editable: false
  }
]);

// raw change
useDevtoolsComponentState((payload, ctx) => {
  payload.state.push(
    ...[
      {
        key: "_bar",
        type: "raw",
        value: "bar",
        editable: true
      },
      {
        key: "_baz",
        type: "raw",
        value: "baz",
        editable: false
      }
    ]
  );
});

Timeline events

To add timeline events:

const id = "vue-composable";
const label = "Test events";
const color = 0x92a2bf;

const { addEvent, pushEvent } = useDevtoolsTimelineLayer(
  id,
  description,
  color
);

// adds event to a specific point in the timeline
addEvent({
  time: Date.now(),
  data: {
    // data object
  },
  meta: {
    // meta object
  }
});

// adds event with `time: Date.now()`
pushEvent({
  data: {
    // data object
  },
  meta: {
    // meta object
  }
});

Inspector

Allows to create a new inspector for your data.

I'm still experimenting on how to expose this API on a composable, this will likely to change in the future, suggestions are welcome.

useDevtoolsInspector(
  {
    id: "vue-composable",
    label: "test vue-composable"
  },
  // list of nodes, this can be reactive
  [
    {
      id: "test",
      label: "test - vue-composable",
      depth: 0,
      state: {
        composable: [
          {
            editable: false,
            key: "count",
            objectType: "Ref",
            type: "setup",
            value: myRefValue
          }
        ]
      }
    }
  ]
);

Typescript

Typescript@3.x is not supported, the supported version can be checked on package.json, usually is the latest version or the same major as vue-3

Contributing

You can contribute raising issues and by helping out with code.

Tests and Documentation are the most important things for me, because good documentation is really useful!

I really appreciate some tweaks or changes on how the documentation is displayed and how to make it easier to read.

Twitter: @pikax_dev

Build

# install packages
yarn

# build and test for v2
yarn build --version=2
yarn test:vue2

# build and test for v3
yarn build
yarn test

New composable

  1. Fork it!
  2. Create your feature branch: git checkout -b feat/new-composable
  3. Commit your changes: git commit -am 'feat(composable): add a new composable'
  4. Push to the branch: git push origin feat/new-composable
  5. Submit a pull request

Documentation

Check our documentation

Composable

Event

  • Event - Attach event listener to a DOM element
  • Mouse Move - Attach mousemove listener to a DOM element
  • Resize - Attach resize listener to a DOM element
  • Scroll - Attach scroll listener to a DOM element
  • onOutsidePress - Execute callback when click is outside of element

Dom

Date

  • useNow : Return reactive custom timer with specified refresh rate
  • useDateNow : Returns reactive Date.now() with custom refresh rate
  • usePerformanceNow : Returns reactive performance.now() with custom refresh rate

Format

  • format - Reactive string format
  • path - Retrieve object value based on string path

Breakpoint

MISC

Storage

  • WebStorage - Reactive access to Storage API, useLocalStorage and useSessionStorage use this
  • storage - uses localStorage or on safari private it uses sessionStorage
  • localStorage - Reactive access to a localStorage
  • sessionStorage - Reactive access to a sessionStorage

Pagination

Validation

i18n

  • i18n - Simple i18n implementation with API inspired by vue-i18n

Intl

Promise

Meta

  • Title - reactive document.title

State

  • Timeline - Tracks variable history
  • Undo - Tracks variable history, to allow undo and redo
  • valueSync - syncs variables value, across multiple refs

Web

External

New packages needed

Information

This is a monorepo project, please check packages

Devtools

There's some experimental devtools support starting from 1.0.0-beta.6, only available for vue-next and devtools beta 6.


Download Details:

Author: Pikax
Source Code: https://github.com/pikax/vue-composable 
License: MIT license

#typescript #pagination #hooks #vuejs #validation 

Vue-composable: Vue Composition-api Composable Components
Lawrence  Lesch

Lawrence Lesch

1675430549

React Hook for using Keyboard Shortcuts in Components

useHotkeys(keys, callback)

npm i react-hotkeys-hook

A React hook for using keyboard shortcuts in components in a declarative way.


Quick Start

The easiest way to use the hook.

import { useHotkeys } from 'react-hotkeys-hook'

export const ExampleComponent = () => {
  const [count, setCount] = useState(0)
  useHotkeys('ctrl+k', () => setCount(count + 1), [count])

  return (
    <p>
      Pressed {count} times.
    </p>
  )
}

Scopes

Scopes allow you to group hotkeys together. You can use scopes to prevent hotkeys from colliding with each other.

const App = () => {
  return (
    <HotkeysProvider initiallyActiveScopes={['settings']}>
      <ExampleComponent />
    </HotkeysProvider>
  )
}

export const ExampleComponent = () => {
  const [count, setCount] = useState(0)
  useHotkeys('ctrl+k', () => setCount(prevCount => prevCount + 1), { scopes: ['settings'] })

  return (
    <p>
      Pressed {count} times.
    </p>
  )
}

Changing a scope's active state

You can change the active state of a scope using the deactivateScope, activateScope and toggleScope functions returned by the useHotkeysContext() hook. Note that you have to have your app wrapped in a <HotkeysProvider> component.

const App = () => {
  return (
          <HotkeysProvider initiallyActiveScopes={['settings']}>
            <ExampleComponent />
          </HotkeysProvider>
  )
}

export const ExampleComponent = () => {
  const { toggleScope } = useHotkeysContext()

  return (
    <button onClick={() => toggleScope('settings')}>
      Change scope active state
    </button>
  )
}

Focus trap

This will only trigger the hotkey if the component is focused.

export const ExampleComponent = () => {
  const [count, setCount] = useState(0)
  const ref = useHotkeys<HTMLParagraphElement>('ctrl+k', () => setCount(prevCount => prevCount + 1))

  return (
    <p tabIndex={-1} ref={ref}>
      Pressed {count} times.
    </p>
  )
}

Documentation & Live Examples

API

useHotkeys(keys, callback)

useHotkeys(keys: string | string[], callback: (event: KeyboardEvent, handler: HotkeysEvent) => void, options: Options = {}, deps: DependencyList = [])
ParameterTypeRequired?Default valueDescription
keysstring or string[]required-set the hotkeys you want the hook to listen to. You can use single or multiple keys, modifier combinations, etc. This will either be a string or an array of strings. To separate multiple keys, use a colon. This split key value can be overridden with the splitKey option.
callback(event: KeyboardEvent, handler: HotkeysEvent) => voidrequired-This is the callback function that will be called when the hotkey is pressed. The callback will receive the browsers native KeyboardEvent and the libraries HotkeysEvent.
optionsOptionsoptional{}Object to modify the behavior of the hook. Default options are given below.
dependenciesDependencyListoptional[]The given callback will always be memoised inside the hook. So if you reference any outside variables, you need to set them here for the callback to get updated (Much like useCallback works in React).

Options

All options are optional and have a default value which you can override to change the behavior of the hook.

OptionTypeDefault valueDescription
enabledboolean or (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => booleantrueThis option determines whether the hotkey is active or not. It can take a boolean (for example a flag from a state outside) or a function which gets executed once the hotkey is pressed. If the function returns false the hotkey won't get executed and all browser events are prevented.
enableOnFormTagsboolean or FormTags[]falseBy default hotkeys are not registered if a focus focuses on an input field. This will prevent accidental triggering of hotkeys when the user is typing. If you want to enable hotkeys, use this option. Setting it to true will enable on all form tags, otherwise you can give an array of form tags to enable the hotkey on (possible options are: ['input', 'textarea', 'select'])
enableOnContentEditablebooleanfalseSet this option to enable hotkeys on tags that have set the contentEditable prop to true
combinationKeystring+Character to indicate keystrokes like shift+c. You might want to change this if you want to listen to the + character like ctrl-+.
splitKeystring,Character to separate different keystrokes like ctrl+a, ctrl+b.
scopesstring or string[]*With scopes you can group hotkeys together. The default scope is the wildcard * which matches all hotkeys. Use the <HotkeysProvider> component to change active scopes.
keyupbooleanfalseDetermines whether to listen to the browsers keyup event for triggering the callback.
keydownbooleantrueDetermines whether to listen to the browsers keydown event for triggering the callback. If you set both keyupand keydown to true, the callback will trigger on both events.
preventDefaultboolean or (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => booleanfalseSet this to a true if you want the hook to prevent the browsers default behavior on certain keystrokes like meta+s to save a page. NOTE: Certain keystrokes are not preventable, like meta+w to close a tab in chrome.
descriptionstringundefinedUse this option to describe what the hotkey does. this is helpful if you want to display a list of active hotkeys to the user.

Overloads

The hooks call signature is very flexible. For example if you don't need to set any special options you can use the dependency array as your third parameter:

useHotkeys('ctrl+k', () => console.log(counter + 1), [counter])

isHotkeyPressed(keys: string | string[], splitKey?: string = ',')

This function allows us to check if the user is currently pressing down a key.

import { isHotkeyPressed } from 'react-hotkeys-hook'

isHotkeyPressed('esc') // Returns true if Escape key is pressed down.

You can also check for multiple keys at the same time:

isHotkeyPressed(['esc', 'ctrl+s']) // Returns true if Escape or Ctrl+S are pressed down.

Support

Found an issue or have a feature request?

Open up an issue or pull request and participate.

Local Development

Checkout this repo, run yarn or npm i and then run the test script to test the behavior of the hook.

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Download Details:

Author: JohannesKlauss
Source Code: https://github.com/JohannesKlauss/react-hotkeys-hook 
License: MIT license

#typescript #javascript #react #hooks #hook 

React Hook for using Keyboard Shortcuts in Components
Bongani  Ngema

Bongani Ngema

1674936720

React-firebase-hooks: React Hooks for Firebase

React Firebase Hooks

A set of reusable React Hooks for Firebase.

This documentation is for v5 of React Firebase Hooks which requires Firebase v9 or higher.

  • For v4 documentation (Firebase v9), see here.
  • For v3 documentation (Firebase v8), see here.
  • For v2 documentation, see here.

Installation

React Firebase Hooks v4 requires React 16.8.0 or later and Firebase v9.0.0 or later.

Whilst previous versions of React Firebase Hooks had some support for React Native Firebase, the underlying changes to v9 of the Firebase Web library have meant this is no longer as straightforward. We will investigate if this is possible in another way as part of a future release.

# with npm
npm install --save react-firebase-hooks

# with yarn
yarn add react-firebase-hooks

This assumes that you’re using the npm or yarn package managers with a module bundler like Webpack or Browserify to consume CommonJS modules.

Why?

This library explores how React Hooks can work to make integration with Firebase even more straightforward than it already is. It takes inspiration for naming from RxFire and is based on an internal library that we had been using in a number of apps prior to the release of React Hooks. The implementation with hooks is 10x simpler than our previous implementation.

Upgrading from v4 to v5

To upgrade your project from v4 to v5 check out the Release Notes which have full details of everything that needs to be changed.

Documentation

Download Details:

Author: CSFrequency
Source Code: https://github.com/CSFrequency/react-firebase-hooks 
License: Apache-2.0 license

#react #firebase #hooks #typescript #javascript 

React-firebase-hooks: React Hooks for Firebase
Lawrence  Lesch

Lawrence Lesch

1673491680

Zent: A Collection Of Essential UI Components Written with React

Zent ( \ˈzent\ ), a collection of essential UI components written with React.

Zent ( \ˈzent\ ) is a React component library developed and used at Youzan. Zent provides a collection of essential UI components and lots of useful domain specific components.

We have more than 50 components for now and we're releasing more.

Our goal is making React development faster and simpler.

🇨🇳 访问中文版

Features

  • High quality React components
  • Builtin TypeScript support
  • Supports custom themes
  • Import JavaScript and styles only if they are used
  • Handmade icon font

Supported Environments

  • React >= 17
  • Modern browsers but not IE
  • Supports server-side rendering(SSR)

Required polyfills

  • es6.object.assign
  • es6.object.is
  • es6.string.ends-with
  • es6.string.starts-with
  • es6.string.includes
  • es7.string.trim-left
  • es7.string.trim-right
  • es6.array.from
  • es6.array.of
  • es6.array.fill
  • es6.array.find
  • es6.array.find-index
  • es7.array.includes

Install

yarn add zent

# or

npm install zent --save

Documentation

https://youzan.github.io/zent/en/guides/install

Contribution

Read our contributing guide to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Zent.

Send issues and pull requests with your ideas.

Download Details:

Author: Youzan
Source Code: https://github.com/youzan/zent 
License: MIT license

#typescript #javascript #react #hooks #components 

Zent: A Collection Of Essential UI Components Written with React
Lawrence  Lesch

Lawrence Lesch

1673463960

UseStateMachine: The <1 Kb State Machine Hook for React

UseStateMachine

The <1 kb state machine hook for React:

See the user-facing docs at: usestatemachine.js.org

  • Batteries Included: Despite the tiny size, useStateMachine is feature complete (Entry/exit callbacks, Guarded transitions & Extended State - Context)
  • Amazing TypeScript experience: Focus on automatic type inference (auto completion for both TypeScript & JavaScript users without having to manually define the typings) while giving you the option to specify and augment the types for context & events.
  • Made for React: useStateMachine follow idiomatic React patterns you and your team are already familiar with. (The library itself is actually a thin wrapper around React's useReducer & useEffect.)

size badge

Examples

Installation

npm install @cassiozen/usestatemachine

Sample Usage

const [state, send] = useStateMachine({
  initial: 'inactive',
  states: {
    inactive: {
      on: { TOGGLE: 'active' },
    },
    active: {
      on: { TOGGLE: 'inactive' },
      effect() {
        console.log('Just entered the Active state');
        // Same cleanup pattern as `useEffect`:
        // If you return a function, it will run when exiting the state.
        return () => console.log('Just Left the Active state');
      },
    },
  },
});

console.log(state); // { value: 'inactive', nextEvents: ['TOGGLE'] }

// Refers to the TOGGLE event name for the state we are currently in.

send('TOGGLE');

// Logs: Just entered the Active state

console.log(state); // { value: 'active', nextEvents: ['TOGGLE'] }

API

useStateMachine

const [state, send] = useStateMachine(/* State Machine Definition */);

useStateMachine takes a JavaScript object as the state machine definition. It returns an array consisting of a current machine state object and a send function to trigger transitions.

state

The machine's state consists of 4 properties: value, event, nextEvents and context.

value (string): Returns the name of the current state.

event ({type: string}; Optional): The name of the last sent event that led to this state.

nextEvents (string[]): An array with the names of available events to trigger transitions from this state.

context: The state machine extended state. See "Extended State" below.

Send events

send takes an event as argument, provided in shorthand string format (e.g. "TOGGLE") or as an event object (e.g. { type: "TOGGLE" })

If the current state accepts this event, and it is allowed (see guard), it will change the state machine state and execute effects.

You can also send additional data with your event using the object notation (e.g. { type: "UPDATE" value: 10 }). Check schema for more information about strong typing the additional data.

State Machine definition

KeyRequiredDescription
verbose If true, will log every context & state changes. Log messages will be stripped out in the production build.
schema For usage with TypeScript only. Optional strongly-typed context & events. More on schema below
context Context is the machine's extended state. More on extended state below
initial*The initial state node this machine should be in
states*Define the possible finite states the state machine can be in.

Defining States

A finite state machine can be in only one of a finite number of states at any given time. As an application is interacted with, events cause it to change state.

States are defined with the state name as a key and an object with two possible keys: on (which events this state responds to) and effect (run arbitrary code when entering or exiting this state):

On (Events & transitions)

Describes which events this state responds to (and to which other state the machine should transition to when this event is sent):

states: {
  inactive: {
    on: {
      TOGGLE: 'active';
    }
  },
  active: {
    on: {
      TOGGLE: 'inactive';
    }
  },
},

The event definition can also use the extended, object syntax, which allows for more control over the transition (like adding guards):

on: {
  TOGGLE: {
    target: 'active',
  },
};

Guards

Guards are functions that run before actually making the state transition: If the guard returns false the transition will be denied.

const [state, send] = useStateMachine({
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: {
          target: 'active',
          guard({ context, event }) {
            // Return a boolean to allow or block the transition
          },
        },
      },
    },
    active: {
      on: { TOGGLE: 'inactive' },
    },
  },
});

The guard function receives an object with the current context and the event. The event parameter always uses the object format (e.g. { type: 'TOGGLE' }).

Effects (entry/exit callbacks)

Effects are triggered when the state machine enters a given state. If you return a function from your effect, it will be invoked when leaving that state (similarly to how useEffect works in React).

const [state, send] = useStateMachine({
  initial: 'active',
  states: {
    active: {
      on: { TOGGLE: 'inactive' },
      effect({ send, setContext, event, context }) {
        console.log('Just entered the Active state');
        return () => console.log('Just Left the Active state');
      },
    },
  },
});

The effect function receives an object as parameter with four keys:

  • send: Takes an event as argument, provided in shorthand string format (e.g. "TOGGLE") or as an event object (e.g. { type: "TOGGLE" })
  • setContext: Takes an updater function as parameter to set a new context (more on context below). Returns an object with send, so you can set the context and send an event on a single line.
  • event: The event that triggered a transition to this state. (The event parameter always uses the object format (e.g. { type: 'TOGGLE' }).).
  • context The context at the time the effect runs.

In this example, the state machine will always send the "RETRY" event when entering the error state:

const [state, send] = useStateMachine({
  initial: 'loading',
  states: {
    /* Other states here... */
    error: {
      on: {
        RETRY: 'load',
      },
      effect({ send }) {
        send('RETRY');
      },
    },
  },
});

Extended state (context)

Besides the finite number of states, the state machine can have extended state (known as context).

You can provide the initial context value in the state machine definition, then use the setContext function within your effects to change the context:

const [state, send] = useStateMachine({
  context: { toggleCount: 0 },
  initial: 'inactive',
  states: {
    inactive: {
      on: { TOGGLE: 'active' },
    },
    active: {
      on: { TOGGLE: 'inactive' },
      effect({ setContext }) {
        setContext(context => ({ toggleCount: context.toggleCount + 1 }));
      },
    },
  },
});

console.log(state); // { context: { toggleCount: 0 }, value: 'inactive', nextEvents: ['TOGGLE'] }

send('TOGGLE');

console.log(state); // { context: { toggleCount: 1 }, value: 'active', nextEvents: ['TOGGLE'] }

Schema: Context & Event Typing

TypeScript will automatically infer your context type; event types are generated automatically.

Still, there are situations where you might want explicit control over the context and event types: You can provide you own typing using the t whithin schema:

Typed Context example

const [state, send] = useStateMachine({
  schema: {
    context: t<{ toggleCount: number }>()
  },
  context: { toggleCount: 0 },
  initial: 'inactive',
  states: {
    inactive: {
      on: { TOGGLE: 'active' },
    },
    active: {
      on: { TOGGLE: 'inactive' },
      effect({ setContext }) {
        setContext(context => ({ toggleCount: context.toggleCount + 1 }));
      },
    },
  },
});

Typed Events

All events are type-infered by default, both in the string notation (send("UPDATE")) and the object notation (send({ type: "UPDATE"})).

If you want, though, you can augment an already typed event to include arbitrary data (which can be useful to provide values to be used inside effects or to update the context). Example:

const [machine, send] = useStateMachine({
  schema: {
    context: t<{ timeout?: number }>(),
    events: {
      PING: t<{ value: number }>()
    }
  },
  context: {timeout: undefined},
  initial: 'waiting',
  states: {
    waiting: {
      on: {
        PING: 'pinged'
      }
    },
    pinged: {
      effect({ setContext, event }) {
        setContext(c => ({ timeout: event?.value ?? 0 }));
      },
    }
  },
});

send({ type: 'PING', value: 150 })

Note that you don't need to declare all your events in the schema, only the ones you're adding arbitrary keys and values.

Wiki

Download Details:

Author: Cassiozen
Source Code: https://github.com/cassiozen/useStateMachine 
License: MIT license

#typescript #hooks #state #management 

UseStateMachine: The <1 Kb State Machine Hook for React
Lawrence  Lesch

Lawrence Lesch

1672996320

Rooks: Essential React Custom Hooks to Super Charge Your Components!

Rooks

Essential React custom hooks ⚓ to super charge your components!

Complete Documentation

Image from Gyazo

Features

✅ Collection of 62 hooks as standalone modules.

✅ Standalone package with all the hooks at one place

✅ CommonJS, UMD and ESM Support

Installation

npm i -s rooks

Import any hook from "rooks" and start using them!

import { useDidMount } from "rooks";

Usage

function App() {
  useDidMount(() => {
    alert("mounted");
  });
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Standalone Package

Package containing all the hooks is over here. - Docs and Npm Install

List of all hooks

 

 

🔥 Effects

 

  • useAsyncEffect - A version of useEffect that accepts an async function
  • useDidMount - componentDidMount hook for React
  • useDidUpdate - componentDidUpdate hook for react
  • useEffectOnceWhen - Runs a callback effect atmost one time when a condition becomes true
  • useIntervalWhen - Sets an interval immediately when a condition is true
  • useIsomorphicEffect - A hook that resolves to useEffect on the server and useLayoutEffect on the client.
  • useLifecycleLogger - A react hook that console logs parameters as component transitions through lifecycles.
  • useWillUnmount - componentWillUnmount lifecycle as hook for React.

 

🚀 Events

 

 

📝 Form

 

 

✨ Misc

 

  • useDebounce - Debounce hook for react
  • useDebouncedValue - Tracks another value and gets updated in a debounced way.
  • useDimensionsRef - Easily grab dimensions of an element with a ref using this hook
  • useEventListenerRef - A react hook to add an event listener to a ref
  • useForkRef - A hook that can combine two refs(mutable or callbackRefs) into a single callbackRef
  • useFreshRef - Avoid stale state in callbacks with this hook. Auto updates values using a ref.
  • useFreshTick - Like use-fresh-ref but specifically for functions
  • useMergeRefs - Merges any number of refs into a single ref
  • useRefElement - Helps bridge gap between callback ref and state
  • useRenderCount - Get the render count of a component
  • useThrottle - Throttle custom hook for React
  • useTimeoutWhen - Takes a callback and fires it when a condition is true
  • useToggle - Toggle (between booleans or custom data)hook for React.

 

🚃 Navigator

 

 

❇️ State

 

  • useArrayState - Array state manager hook for React
  • useCountdown - Count down to a target timestamp and call callbacks every second (or provided peried)
  • useCounter - Counter hook for React.
  • useGetIsMounted - Checks if a component is mounted or not at the time. Useful for async effects
  • useLocalstorageState - UseState but auto updates values to localStorage
  • useMapState - A react hook to manage state in a key value pair map.
  • useMultiSelectableList - A custom hook to easily select multiple values from a list
  • usePreviousDifferent - usePreviousDifferent returns the last different value of a variable
  • usePreviousImmediate - usePreviousImmediate returns the previous value of a variable even if it was the same or different
  • useQueueState - A React hook that manages state in the form of a queue
  • useSelect - Select values from a list easily. List selection hook for react.
  • useSelectableList - Easily select a single value from a list of values. very useful for radio buttons, select inputs etc.
  • useSessionstorageState - useState but syncs with sessionstorage
  • useSetState - Manage the state of a Set in React.
  • useStackState - A React hook that manages state in the form of a stack
  • useTimeTravelState - A hook that manages state which can undo and redo. A more powerful version of useUndoState hook.
  • useUndoState - Drop in replacement for useState hook but with undo functionality.

 

⚛️ UI

 

  • useBoundingclientrect - getBoundingClientRect hook for React.
  • useBoundingclientrectRef - A hook that tracks the boundingclientrect of an element. It returns a callbackRef so that the element node if changed is easily tracked.
  • useFullscreen - Use full screen api for making beautiful and emersive experinces.
  • useGeolocation - A hook to provide the geolocation info on client side.
  • useInViewRef - Simple hook that monitors element enters or leave the viewport that's using Intersection Observer API.
  • useIntersectionObserverRef - A hook to register an intersection observer listener.
  • useKey - keypress, keyup and keydown event handlers as hooks for react.
  • useKeyBindings - useKeyBindings can bind multiple keys to multiple callbacks and fire the callbacks on key press.
  • useKeyRef - Very similar useKey but it returns a ref
  • useKeys - A hook which allows to setup callbacks when a combination of keys are pressed at the same time.
  • useMediaMatch - Signal whether or not a media query is currently matched.
  • useMouse - Mouse position hook for React.
  • useMutationObserver - Mutation Observer hook for React.
  • useMutationObserverRef - A hook that tracks mutations of an element. It returns a callbackRef.
  • useRaf - A continuously running requestAnimationFrame hook for React
  • useResizeObserverRef - Resize Observer hook for React.
  • useWindowScrollPosition - A React hook to get the scroll position of the window
  • useWindowSize - Window size hook for React.

Download Details:

Author: imbhargav5
Source Code: https://github.com/imbhargav5/rooks 
License: MIT license

#typescript #react #hooks

Rooks: Essential React Custom Hooks to Super Charge Your Components!
Kevon  Krajcik

Kevon Krajcik

1672972440

How to Create Real Time Todo App using React Hooks

In this React article, we will learn How to Create Real Time Todo App using React Hooks. In this article, we will build a todo application using functional components and Hooks to manage state, here’s a display of what we will have at the end of this tutorial:

react-hooks-todo-demo-1

In a previous article, we introduced React Hooks and looked at some ways to use the useState() and useEffect() methods. If you aren’t already familiar with these methods and their uses, please refer to this article.

Let’s get started.

Prerequisites

To follow along with this tutorial, you’ll need the following tool create-react-app installed.

To get the most out of this tutorial, you need knowledge of JavaScript and the React framework. If you want to play around with React Hooks, you will need the alpha build of React as this feature is still in alpha (as at the time of writing this article).

Setup

Let’s create a new React application using the create-react-app CLI tool:

    $ npx create-react-app react-todo-hooks
    $ cd react-todo-hooks
    $ npm install --save react@16.7.0-alpha.2 react-dom@16.7.0-alpha.2
    $ npm start

We run the command on the third line because we want to install specific versions of react and react-dom (currently in alpha) in order to tap into React Hooks

Running the last command will start the development server on port 3000 and open up a new page on our web browser:

react-hooks-todo-demo-2

We will create a components folder in the src directory and add two files within it:

  1. Todo.js - This is where all of our functional components will go.
  2. Todo.css - This is where the styles for the application will go.

Open the Todo.css file and paste in the following CSS:

    /* File: src/components/Todo.css */
    
    body {
      background: rgb(255, 173, 65);
    }
    
    .todo-container {
      background: rgb(41, 33, 33);
      width: 40vw;
      margin: 10em auto;
      border-radius: 15px;
      padding: 20px 10px;
      color: white;
      border: 3px solid rgb(36, 110, 194);
    }
    
    .task {
      border: 1px solid white;
      border-radius: 5px;
      padding: 0.5em;
      margin: 0.5em;
    }
    
    .task button{
      background: rgb(12, 124, 251);
      border-radius: 5px;
      margin: 0px 5px;
      padding: 3px 5px;
      border: none;
      cursor: pointer;
      color: white;
      float: right;
    }
    
    .header {
      margin: 0.5em;
      font-size: 2em;
      text-align: center;
    }
    
    .create-task input[type=text] {
      margin: 2.5em 2em;
      width: 80%;
      outline: none;
      border: none;
      padding: 0.7em;
    }

Now we want to create two functional components in the Todo.js file:

    // Todo.js
    
    import React, { useState } from 'react';
    import './Todo.css';
    
    function Task({ task }) {
        return (
            <div
                className="task"
                style={{ textDecoration: task.completed ? "line-through" : "" }}
            >
                {task.title}
            </div>
        );
    }
    function Todo() {
        const [tasks, setTasks] = useState([
            {
                title: "Grab some Pizza",
                completed: true
            },
            {
                title: "Do your workout",
                completed: true
            },
            {
                title: "Hangout with friends",
                completed: false
            }
        ]);
        return (
            <div className="todo-container">
                <div className="header">TODO - ITEMS</div>
                <div className="tasks">
                    {tasks.map((task, index) => (
                        <Task
                            task={task}
                            index={index}
                            key={index}
                        />
                    ))}
                </div>
            </div>
        );
    }
    
    export default Todo;

At the beginning of this snippet, we pulled in useState from the React library because we need it to manage the state within our functional components. Next, the Task component returns some JSX to define what each task element will look like.

In the Todo component, the useState function returns an array with two elements. The first item being the current state value for the tasks and the second being a function that can be used to update the tasks:

    const [tasks, setTasks] = useState([
        {
            title: "Grab some Pizza",
            completed: true
        },
        {
            title: "Do your workout",
            completed: true
        },
        {
            title: "Hangout with friends",
            completed: false
        }
    ]);

We finally return some JSX within the Todo component and nest the Task component.

Running the application

For us to see what we’ve done so far, we have to update the index.js file so that it knows where our Todo component is and how to render it to the DOM. Open the index.js file and update it with the following snippet:

    // File: index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import Todo from './components/Todo';
    import * as serviceWorker from './serviceWorker';
    
    ReactDOM.render(<Todo />, document.getElementById('root'));
    
    serviceWorker.unregister();

Now we can save the file and start the development server (if it isn’t already running):

react-hooks-todo-demo-3

We get three hard-coded tasks, two of which are complete and one that isn’t. In the next section, we will work towards making the application interactive and able to receive input from the user.

Creating a new task

Our application currently works with hard-coded data and has no way to receive input in realtime, we will change that now. Let’s create a new functional component and call it CreateTask:

    // Todo.js
    
    // [...]
    
    function CreateTask({ addTask }) {
        const [value, setValue] = useState("");
    
        const handleSubmit = e => {
            e.preventDefault();
            if (!value) return;
            
            addTask(value);
            setValue("");
        }
        
        return (
            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    className="input"
                    value={value}
                    placeholder="Add a new task"
                    onChange={e => setValue(e.target.value)}
                />
            </form>
        );
    }
    
    // [..]

Using useState, this component registers a state — value — and a function for updating it — setValue. The handleSubmit handler will prevent the default action that would normally be taken on the form and add a new Task using the latest value that is in the input field.

The CreateTask component receives a prop addTask, which is basically the function that adds a new task to the tasks state on the Todo component. We want to define this function and also update the JSX of the Todo component so it includes the CreateTask component. Let’s completely replace the code for the Todo component with this one:

    // File: Todo.js
    
    // [...]
    
    function Todo() {
        const [tasks, setTasks] = useState([
            {
                title: "Grab some Pizza",
                completed: true
            },
            {
                title: "Do your workout",
                completed: true
            },
            {
                title: "Hangout with friends",
                completed: false
            }
        ]);
    
        const addTask = title => {
            const newTasks = [...tasks, { title, completed: false }];
            setTasks(newTasks);
        };
    
        return (
            <div className="todo-container">
                <div className="header">TODO - ITEMS</div>
                <div className="tasks">
                    {tasks.map((task, index) => (
                        <Task
                            task={task}
                            index={index}
                            key={index}
                        />
                    ))}
                </div>
                <div className="create-task" >
                    <CreateTask addTask={addTask} />
                </div>
            </div>
        );
    }
    
    // [..]

We’ve included the addTask method here:

    const addTask = title => {
        const newTasks = [...tasks, { title, completed: false }];
        setTasks(newTasks);
    };

We can now save our changes and start the development server again (if it isn’t already running):

react-hooks-todo-demo-4

Now we have a nice input box where we can put in new values to create new tasks for the Todo application.

Completing a task

At this point, we need to be able to indicate that we have completed a task. Our tasks object in the Todo component already makes that possible as there is a completed key-value pair. What we need now is an interactive way for the user to set a task as completed without hard-coding the data.

The first thing we will do here is to update the Task component to receive a new prop and include a Complete button:

    // Todo.js
    
    // [...]
    
    function Task({ task, index, completeTask }) {
        return (
            <div
                className="task"
                style={{ textDecoration: task.completed ? "line-through" : "" }}
            >
                {task.title}
                <button onClick={() => completeTask(index)}>Complete</button>
            </div>
        );
    }
    
    // [..]

Then we will also update the Todo component to define the completeTask method and pass it down as a prop to the Task component in the JSX:

    // File: Todo.js
    
    // [...]
    
    function Todo() {
        const [tasks, setTasks] = useState([
            {
                title: "Grab some Pizza",
                completed: true
            },
            {
                title: "Do your workout",
                completed: true
            },
            {
                title: "Hangout with friends",
                completed: false
            }
        ]);
    
        const addTask = title => {
            const newTasks = [...tasks, { title, completed: false }];
            setTasks(newTasks);
        };
    
        const completeTask = index => {
            const newTasks = [...tasks];
            newTasks[index].completed = true;
            setTasks(newTasks);
        };
    
        return (
            <div className="todo-container">
                <div className="header">TODO - ITEMS</div>
                <div className="tasks">
                    {tasks.map((task, index) => (
                        <Task
                        task={task}
                        index={index}
                        completeTask={completeTask}
                        key={index}
                        />
                    ))}
                </div>
                <div className="create-task" >
                    <CreateTask addTask={addTask} />
                </div>
            </div>
        );
    }
    
    // [...]

We can now start the development server and see what new features have been added:

react-hooks-todo-demo-5

Now we can click on a complete button to indicate that we have finished executing a task!

Removing a task

Another wonderful feature to include to the Todo application is an option to completely remove a task whether it has been completed or not. We can do this in similar steps like the ones we used in creating the complete feature.

Let’s start by updating the Task component to receive a removeTask prop and include an “X” button that deletes a task on click:

    // File: Todo.js
    
    // [...]
    
    function Task({ task, index, completeTask, removeTask }) {
        return (
            <div
                className="task"
                style={{ textDecoration: task.completed ? "line-through" : "" }}
            >
                {task.title}
                <button style={{ background: "red" }} onClick={() => removeTask(index)}>x</button>
                <button onClick={() => completeTask(index)}>Complete</button>
            </div>
        );
    }
    
    // [...]

Now we can update the Todo component to register the removeTask method and pass it down as a prop to the Task component in the JSX:

    // File: Todo.js
    
    // [...]
    
    function Todo() {
        const [tasks, setTasks] = useState([
            {
                title: "Grab some Pizza",
                completed: true
            },
            {
                title: "Do your workout",
                completed: true
            },
            {
                title: "Hangout with friends",
                completed: false
            }
        ]);
    
        const addTask = title => {
            const newTasks = [...tasks, { title, completed: false }];
            setTasks(newTasks);
        };
    
        const completeTask = index => {
            const newTasks = [...tasks];
            newTasks[index].completed = true;
            setTasks(newTasks);
        };
    
        const removeTask = index => {
            const newTasks = [...tasks];
            newTasks.splice(index, 1);
            setTasks(newTasks);
        };
    
        return (
            <div className="todo-container">
                <div className="header">TODO - ITEMS</div>
                <div className="tasks">
                    {tasks.map((task, index) => (
                        <Task
                        task={task}
                        index={index}
                        completeTask={completeTask}
                        removeTask={removeTask}
                        key={index}
                        />
                    ))}
                </div>
                <div className="create-task" >
                    <CreateTask addTask={addTask} />
                </div>
            </div>
        );
    }
    
    // [...]

We can now test out the new functionality:

react-hooks-todo-demo-6

Great, we have a fully functional Todo application that is built off functional components only. We will add an additional feature in the next section.

Using useEffect to monitor the number of uncompleted tasks remaining

In this section, we will use the useEffect state Hook to update the number of pending tasks whenever the DOM is re-rendered. You can learn more about the useEffect hook here.

First of all, we need to pull in useEffect from the react library:

    import React, { useState, useEffect } from 'react';

Then we will register a new state Hook for the pending tasks in the Todo component:

    const [tasksRemaining, setTasksRemaining] = useState(0);

We will also add an effect hook to update the state of tasksRemaining when the DOM re-renders:

    useEffect(() => { setTasksRemaining(tasks.filter(task => !task.completed).length) });

Finally, we will update the JSX in the Todo component to reactively display the number of pending tasks. Here’s what the Todo component should look like:

    // File: Todo.js
    
    // [...]
    
    function Todo() {
        const [tasksRemaining, setTasksRemaining] = useState(0);
        const [tasks, setTasks] = useState([
            {
                title: "Grab some Pizza",
                completed: true
            },
            {
                title: "Do your workout",
                completed: true
            },
            {
                title: "Hangout with friends",
                completed: false
            }
        ]);
          
        useEffect(() => { 
          setTasksRemaining(tasks.filter(task => !task.completed).length) 
        });
    
        const addTask = title => {
            const newTasks = [...tasks, { title, completed: false }];
            setTasks(newTasks);
        };
        
        const completeTask = index => {
            const newTasks = [...tasks];
            newTasks[index].completed = true;
            setTasks(newTasks);
        };
        
        const removeTask = index => {
            const newTasks = [...tasks];
            newTasks.splice(index, 1);
            setTasks(newTasks);
        };
    
        return (
            <div className="todo-container">
                <div className="header">Pending tasks ({tasksRemaining})</div>
                <div className="tasks">
                    {tasks.map((task, index) => (
                        <Task
                        task={task}
                        index={index}
                        completeTask={completeTask}
                        removeTask={removeTask}
                        key={index}
                        />
                    ))}
                </div>
                <div className="create-task" >
                    <CreateTask addTask={addTask} />
                </div>
            </div>
        );
    }
    
    // [...]

We can test that the application displays the pending tasks correctly:

react-hooks-todo-demo-1

Conclusion

In this tutorial, we have learned how we can create a simple todo application using React Hooks. Hooks are a very welcome feature to React and it allows new levels of modularization that was not previously possible in React.

Original article sourced at: https://pusher.com

#react #hooks 

How to Create Real Time Todo App using React Hooks
Lawrence  Lesch

Lawrence Lesch

1672756997

React-firebase-hooks: React Hooks for Firebase

React Firebase Hooks

A set of reusable React Hooks for Firebase.

This documentation is for v5 of React Firebase Hooks which requires Firebase v9 or higher.

  • For v4 documentation (Firebase v9), see here.
  • For v3 documentation (Firebase v8), see here.
  • For v2 documentation, see here.

Installation

React Firebase Hooks v4 requires React 16.8.0 or later and Firebase v9.0.0 or later.

Whilst previous versions of React Firebase Hooks had some support for React Native Firebase, the underlying changes to v9 of the Firebase Web library have meant this is no longer as straightforward. We will investigate if this is possible in another way as part of a future release.

# with npm
npm install --save react-firebase-hooks

# with yarn
yarn add react-firebase-hooks

This assumes that you’re using the npm or yarn package managers with a module bundler like Webpack or Browserify to consume CommonJS modules.

Why?

This library explores how React Hooks can work to make integration with Firebase even more straightforward than it already is. It takes inspiration for naming from RxFire and is based on an internal library that we had been using in a number of apps prior to the release of React Hooks. The implementation with hooks is 10x simpler than our previous implementation.

Upgrading from v4 to v5

To upgrade your project from v4 to v5 check out the Release Notes which have full details of everything that needs to be changed.

Documentation

Download Details:

Author: CSFrequency
Source Code: https://github.com/CSFrequency/react-firebase-hooks 
License: Apache-2.0 license

#typescript #javascript #react #firebase #hooks 

React-firebase-hooks: React Hooks for Firebase
Nigel  Uys

Nigel Uys

1672340640

Ansible Role to Deploy Scripting Apps Like PHP, Python, Ruby, Etc

Ansistrano

ansistrano.deploy and ansistrano.rollback are Ansible roles to easily manage the deployment process for scripting applications such as PHP, Python and Ruby. It's an Ansible port for Capistrano.

History

Capistrano is a remote server automation tool and it's currently in Version 3. Version 2.0 was originally thought in order to deploy RoR applications. With additional plugins, you were able to deploy non Rails applications such as PHP and Python, with different deployment strategies, stages and much more. I loved Capistrano v2. I have used it a lot. I developed a plugin for it.

Capistrano 2 was a great tool and it still works really well. However, it is not maintained anymore since the original team is working in v3. This new version does not have the same set of features so it is less powerful and flexible. Besides that, other new tools are becoming easier to use in order to deploy applications, such as Ansible.

So, I have decided to stop using Capistrano because v2 is not maintained, v3 does not have enough features, and I can do everything Capistrano was doing with Ansible. If you are looking for alternatives, check Fabric or Chef Solo.

Project name

Ansistrano comes from Ansible + Capistrano, easy, isn't it?

Ansistrano anonymous usage stats

There is an optional step in Ansistrano that sends a HTTP request to our servers. Unfortunately, the metrics we can get from Ansible Galaxy are limited so this is one of the few ways we have to measure how many active users we really have.

We only use this data for usage statistics but anyway, if you are not comfortable with this, you can disable this extra step by setting ansistrano_allow_anonymous_stats to false in your playbooks.

Who is using Ansistrano?

Is Ansistrano ready to be used? Here are some companies currently using it:

If you are also using it, please let us know via a PR to this document.

Requirements

In order to deploy your apps with Ansistrano, you will need:

  • Ansible in your deployer machine
  • rsync on the target machine if you are using either the rsync, rsync_direct, or git deployment strategy or if you are using ansistrano_current_via = rsync

Installation

Ansistrano is an Ansible role distributed globally using Ansible Galaxy. In order to install Ansistrano role you can use the following command.

$ ansible-galaxy install ansistrano.deploy ansistrano.rollback

Update

If you want to update the role, you need to pass --force parameter when installing. Please, check the following command:

$ ansible-galaxy install --force ansistrano.deploy ansistrano.rollback

Features

  • Rollback in seconds (with ansistrano.rollback role)
  • Customize your deployment with hooks before and after critical steps
  • Save disk space keeping a maximum fixed releases in your hosts
  • Choose between SCP, RSYNC, GIT, SVN, HG, HTTP Download or S3 GET deployment strategies (optional unarchive step included)

Main workflow

Ansistrano deploys applications following the Capistrano flow.

  • Setup phase: Creates the folder structure to hold your releases
  • Code update phase: Puts the new release into your hosts
  • Symlink phase: After deploying the new release into your hosts, this step changes the current softlink to new the release
  • Cleanup phase: Removes any old version based in the ansistrano_keep_releases parameter (see "Role Variables")

Ansistrano Flow

Role Variables

vars:
  ansistrano_deploy_from: "{{ playbook_dir }}/" # Where my local project is (relative or absolute path)
  ansistrano_deploy_to: "/var/www/my-app" # Base path to deploy to.
  ansistrano_version_dir: "releases" # Releases folder name
  ansistrano_shared_dir: "shared" # Shared folder name
  ansistrano_current_dir: "current" # Softlink name. You should rarely changed it.
  ansistrano_current_via: "symlink" # Deployment strategy who code should be deployed to current path. Options are symlink or rsync
  ansistrano_keep_releases: 0 # Releases to keep after a new deployment. See "Pruning old releases".

  # Arrays of directories and files to be shared.
  # The following arrays of directories and files will be symlinked to the current release directory after the 'update-code' step and its callbacks
  # Notes:
  # * Paths are relative to the shared directory (no starting /)
  # * If your items are in a subdirectory, write the entire path to each shared directory
  #
  # Example:
  # ansistrano_shared_paths:
  #   - path/to/first-dir
  #   - path/next-dir
  # ansistrano_shared_files:
  #   - my-file.txt
  #   - path/to/file.txt
  ansistrano_shared_paths: []
  ansistrano_shared_files: []


  # Shared paths and basedir shared files creation.
  # By default the shared paths directories and base directories for shared files are created automatically if not exists. But in some scenarios those paths could be symlinks to another directories in the filesystem, and the deployment process would fails. With these variables you can disable the involved tasks. If you have two or three shared paths, and don't need creation only for some of them, you always could disable the automatic creation and add a custom task in a hook.
  ansistrano_ensure_shared_paths_exist: yes
  ansistrano_ensure_basedirs_shared_files_exist: yes
  
  # Deployment strategy - method used to deliver code. Options are copy, download, git, rsync, rsync_direct, svn, or s3. 
  ansistrano_deploy_via: rsync 
  # Copy, download and s3 have an optional step to unarchive the downloaded file which can be used by adding _unarchive. 
  # The rsync_direct strategy omits a file copy on the target offering a slight speed increase if you are deploying to shared hosts, are experiancing bad file-performance, or serve static assets from the same host you deploy your app to and rsync many files.
  # You can check all the options inside tasks/update-code folder!
  
  ansistrano_allow_anonymous_stats: yes

  # Variables used in the rsync/rsync_direct deployment strategy
  ansistrano_rsync_extra_params: "" # Extra parameters to use when deploying with rsync in a single string. Although Ansible allows an array this can cause problems if we try to add multiple --include args as it was reported in https://github.com/ansistrano/deploy/commit/e98942dc969d4e620313f00f003a7ea2eab67e86
  ansistrano_rsync_set_remote_user: yes # See [ansible synchronize module](http://docs.ansible.com/ansible/synchronize_module.html). Options are yes, no.
  ansistrano_rsync_path: "" # See [ansible synchronize module](http://docs.ansible.com/ansible/synchronize_module.html). By default is "sudo rsync", it can be overwriten with (example): "sudo -u user rsync".
  ansistrano_rsync_use_ssh_args: no # See [ansible synchronize module](http://docs.ansible.com/ansible/synchronize_module.html). If set yes, use the ssh_args specified in ansible.cfg.

  # Variables used in the Git deployment strategy
  ansistrano_git_repo: git@github.com:USERNAME/REPO.git # Location of the git repository
  ansistrano_git_branch: master # What version of the repository to check out. This can be the full 40-character SHA-1 hash, the literal string HEAD, a branch name, or a tag name
  ansistrano_git_repo_tree: "" # If specified the subtree of the repository to deploy
  ansistrano_git_identity_key_path: "" # If specified this file is copied over and used as the identity key for the git commands, path is relative to the playbook in which it is used
  ansistrano_git_identity_key_remote_path: "" # If specified this file on the remote server is used as the identity key for the git commands, remote path is absolute
  ansistrano_git_identity_key_shred: true # Shred identity key by default but can be overloaded to false if you encounter the following issue (https://github.com/ansistrano/deploy/issues/357)
  # Optional variables, omitted by default
  ansistrano_git_refspec: ADDITIONAL_GIT_REFSPEC # Additional refspec to be used by the 'git' module. Uses the same syntax as the 'git fetch' command.
  ansistrano_git_ssh_opts: "-o StrictHostKeyChecking=no" # Additional ssh options to be used in Git
  ansistrano_git_depth: 1 # Additional history truncated to the specified number or revisions
  ansistrano_git_executable: /opt/local/bin/git # Path to git executable to use. If not supplied, the normal mechanism for resolving binary paths will be used.

  # Variables used in the SVN deployment strategy
  # Please note there was a bug in the subversion module in Ansible 1.8.x series (https://github.com/ansible/ansible-modules-core/issues/370) so it is only supported from Ansible 1.9
  ansistrano_svn_repo: https://svn.company.com/project # Location of the svn repository
  ansistrano_svn_branch: trunk # What branch from the repository to check out.
  ansistrano_svn_revision: HEAD # What revision from the repository to check out.
  ansistrano_svn_username: user # SVN authentication username
  ansistrano_svn_password: Pa$$word # SVN authentication password
  ansistrano_svn_environment: {} # Dict with environment variables for svn tasks (https://docs.ansible.com/ansible/playbooks_environment.html)

  # Variables used in the HG deployment strategy
  ansistrano_hg_repo: https://USERNAME@bitbucket.org/USERNAME/REPO # Location of the hg repo
  ansistrano_hg_branch: default # Any branch identifier that works with hg -r, so named branch, bookmark, commit hash...

  # Variables used in the download deployment strategy
  ansistrano_get_url: https://github.com/someproject/somearchive.tar.gz
  ansistrano_download_force_basic_auth: false # no default as this is only supported from Ansible 2.0
  ansistrano_download_headers: "" # no default as this is only supported from Ansible 2.0

  # Variables used in the S3 deployment strategy
  ansistrano_s3_bucket: s3bucket
  ansistrano_s3_object: s3object.tgz # Add the _unarchive suffix to the ansistrano_deploy_via if your object is a package (ie: s3_unarchive)
  ansistrano_s3_region: eu-west-1
  ansistrano_s3_rgw: false # must be Ansible >= 2.2. use Ceph RGW for S3 compatible cloud providers
  ansistrano_s3_url: http://rgw.example.com # when use Ceph RGW, set url
  # Optional variables, omitted by default
  ansistrano_s3_aws_access_key: YOUR_AWS_ACCESS_KEY
  ansistrano_s3_aws_secret_key: YOUR_AWS_SECRET_KEY
  ansistrano_s3_ignore_nonexistent_bucket: false
  
  # Variables used in the GCS deployment strategy
  ansistrano_gcs_bucket: gcsbucket
  ansistrano_gcs_object: gcsobject.tgz # Add the _unarchive suffix to the ansistrano_deploy_via if your object is a package (ie: s3_unarchive)
  ansistrano_gcs_region: eu-west-1 # https://cloud.google.com/storage/docs/bucket-locations
  # Optional variables, omitted by default
  ansistrano_gcs_access_key: YOUR_GCS_ACCESS_KEY # navigate to Cloud console > Storage > Settings > Interoperability
  ansistrano_gcs_secret_key: YOUR_GCS_SECRET_KEY

  # Hooks: custom tasks if you need them
  ansistrano_before_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-setup-tasks.yml"
  ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-setup-tasks.yml"
  ansistrano_before_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-update-code-tasks.yml"
  ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-update-code-tasks.yml"
  ansistrano_before_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-shared-tasks.yml"
  ansistrano_after_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-shared-tasks.yml"
  ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-tasks.yml"
  ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-tasks.yml"
  ansistrano_before_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-cleanup-tasks.yml"
  ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-cleanup-tasks.yml"

{{ playbook_dir }} is an Ansible variable that holds the path to the current playbook.

Deploying

In order to deploy with Ansistrano, you need to perform some steps:

  • Create a new hosts file. Check ansible inventory documentation if you need help. This file will identify all the hosts where to deploy to. For multistage environments check Multistage environments.
  • Create a new playbook for deploying your app, for example, deploy.yml
  • Set up role variables (see Role Variables)
  • Include the ansistrano.deploy role as part of a play
  • Run the deployment playbook

ansible-playbook -i hosts deploy.yml

If everything has been set up properly, this command will create the following approximate directory structure on your server. Check how the hosts folder structure would look like after one, two and three deployments.

-- /var/www/my-app.com
|-- current -> /var/www/my-app.com/releases/20100509145325
|-- releases
|   |-- 20100509145325
|-- shared
-- /var/www/my-app.com
|-- current -> /var/www/my-app.com/releases/20100509150741
|-- releases
|   |-- 20100509150741
|   |-- 20100509145325
|-- shared
-- /var/www/my-app.com
|-- current -> /var/www/my-app.com/releases/20100512131539
|-- releases
|   |-- 20100512131539
|   |-- 20100509150741
|   |-- 20100509145325
|-- shared

Serial deployments

To prevent different timestamps when deploying to several servers using the serial option, you should set the ansistrano_release_version variable.

ansible-playbook -i hosts -e "ansistrano_release_version=`date -u +%Y%m%d%H%M%SZ`" deploy.yml

Rolling back

In order to rollback with Ansistrano, you need to set up the deployment and run the rollback playbook.

ansible-playbook -i hosts rollback.yml

If you try to rollback with zero or one releases deployed, an error will be raised and no actions performed.

Variables you can tune in rollback role are less than in deploy one:

vars:
  ansistrano_deploy_to: "/var/www/my-app" # Base path to deploy to.
  ansistrano_version_dir: "releases" # Releases folder name
  ansistrano_current_dir: "current" # Softlink name. You should rarely changed it.
  ansistrano_rollback_to_release: "" # If specified, the application will be rolled back to this release version; previous release otherwise.
  ansistrano_remove_rolled_back: yes # You can change this setting in order to keep the rolled back release in the server for later inspection
  ansistrano_allow_anonymous_stats: yes

  # Hooks: custom tasks if you need them
  ansistrano_rollback_before_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-rollback-before-setup-tasks.yml"
  ansistrano_rollback_after_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-rollback-after-setup-tasks.yml"
  ansistrano_rollback_before_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-rollback-before-symlink-tasks.yml"
  ansistrano_rollback_after_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-rollback-after-symlink-tasks.yml"
  ansistrano_rollback_before_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-rollback-before-cleanup-tasks.yml"
  ansistrano_rollback_after_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-rollback-after-cleanup-tasks.yml"

Multistage environment (devel, preprod, prod, etc.)

If you want to deploy to different environments such as devel, preprod and prod, it's recommended to create different hosts files. When done, you can specify a different host file when running the deployment playbook using the -i parameter. On every host file, you can specify different users, password, connection parameters, etc.

ansible-playbook -i hosts_devel deploy.yml

ansible-playbook -i hosts_preprod deploy.yml

ansible-playbook -i hosts_prod deploy.yml

Hooks: Custom tasks

You will typically need to reload your webserver after the Symlink step, or download your dependencies before Code update or even do it in production before the Symlink. So, in order to perform your custom tasks you have some hooks that Ansistrano will execute before and after each of the main 3 steps. This is the main benefit against other similar deployment roles.

-- /my-local-machine/my-app.com
|-- hosts
|-- deploy.yml
|-- my-custom-tasks
|   |-- before-code-update.yml
|   |-- after-code-update.yml
|   |-- before-symlink.yml
|   |-- after-symlink.yml
|   |-- before-cleanup.yml
|   |-- after-cleanup.yml

For example, in order to restart apache after Symlink step, we'll add in the after-symlink.yml

- name: Restart Apache
  service: name=httpd state=reloaded
  • Q: Where would you add sending email notification after a deployment?
  • Q: (for PHP and Symfony developers) Where would you clean the cache?

You can specify a custom tasks file for before and after every step using ansistrano_before_*_tasks_file and ansistrano_after_*_tasks_file role variables. See "Role Variables" for more information.

Variables in custom tasks

When writing your custom tasks files you may need some variables that Ansistrano makes available to you:

  • {{ ansistrano_release_path.stdout }}: Path to current deployment release (probably the one you are going to use the most)
  • {{ ansistrano_releases_path }}: Path to releases folder
  • {{ ansistrano_shared_path }}: Path to shared folder (where common releases assets can be stored)
  • {{ ansistrano_release_version }}: Relative directory name for the release (by default equals to the current timestamp in UTC timezone)

Pruning old releases

In continuous delivery environments, you will possibly have a high number of releases in production. Maybe you have tons of space and you don't mind, but it's common practice to keep just a custom number of releases.

After the deployment, if you want to remove old releases just set the ansistrano_keep_releases variable to the total number of releases you want to keep.

Let's see three deployments with an ansistrano_keep_releases: 2 configuration:

-- /var/www/my-app.com
|-- current -> /var/www/my-app.com/releases/20100509145325
|-- releases
|   |-- 20100509145325
|-- shared
-- /var/www/my-app.com
|-- current -> /var/www/my-app.com/releases/20100509150741
|-- releases
|   |-- 20100509150741
|   |-- 20100509145325
|-- shared
-- /var/www/my-app.com
|-- current -> /var/www/my-app.com/releases/20100512131539
|-- releases
|   |-- 20100512131539
|   |-- 20100509150741
|-- shared

See how the release 20100509145325 has been removed.

Example Playbook

In the folder, example you can check an example project that shows how to deploy a small application with Ansistrano.

In order to run it, you will need to have Vagrant and the ansistrano roles installed. Please check https://www.vagrantup.com for more information about Vagrant and our Installation section.

$ cd example/my-playbook
$ vagrant up
$ ansible-playbook -i hosts deploy.yml

And after running these commands, the index.html located in the my-app folder will be deployed to both vagrant boxes

In order to test the rollback playbook, you will need to run deploy.yml at least twice (so that there is something to rollback to). And once this is done, you only need to run

$ ansible-playbook -i hosts rollback.yml

You can check more advanced examples inside the test folder which are run against Travis-CI

Sample projects

We have added Ansistrano support for other projects we are working on.

As an example, see the execution log of the LastWishes deployment:

PLAY [Deploy last wishes app to my server] ************************************

GATHERING FACTS ***************************************************************
ok: [quepimquepam.com]

TASK: [ansistrano.deploy | Ensure deployment base path exists] ***
ok: [quepimquepam.com]

TASK: [ansistrano.deploy | Ensure releases folder exists] ***
ok: [quepimquepam.com]

TASK: [ansistrano.deploy | Ensure shared elements folder exists] ***
ok: [quepimquepam.com]

TASK: [ansistrano.deploy | Get release timestamp] ***********
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Get release path] ****************
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Get releases path] ***************
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Get shared path (in rsync case)] ***
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Rsync application files to remote shared copy (in rsync case)] ***
changed: [quepimquepam.com -> 127.0.0.1]

TASK: [ansistrano.deploy | Deploy existing code to servers] ***
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Deploy existing code to remote servers] ***
skipping: [quepimquepam.com]

TASK: [ansistrano.deploy | Update remote repository] ********
skipping: [quepimquepam.com]

TASK: [ansistrano.deploy | Export a copy of the repo] *******
skipping: [quepimquepam.com]

TASK: [ansistrano.deploy | Deploy code from to servers] *****
skipping: [quepimquepam.com]

TASK: [ansistrano.deploy | Copy release version into REVISION file] ***
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Touches up the release code] *****
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Change softlink to new release] ***
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Reload Apache] *******************
changed: [quepimquepam.com]

TASK: [ansistrano.deploy | Clean up releases] ***************
skipping: [quepimquepam.com]

PLAY RECAP ********************************************************************
quepimquepam.com           : ok=14   changed=10   unreachable=0    failed=0

They're talking about us

Other resources

Download Details:

Author: Ansistrano
Source Code: https://github.com/ansistrano/deploy 
License: MIT license

#ansible #hooks #deployment

Ansible Role to Deploy Scripting Apps Like PHP, Python, Ruby, Etc
Lawrence  Lesch

Lawrence Lesch

1672166940

Kbar: Fast, Portable, and Extensible Cmd+k interface for Your Site

kbar

kbar is a simple plug-n-play React component to add a fast, portable, and extensible command + k (command palette) interface to your site.

demo

Background

Command + k interfaces are used to create a web experience where any type of action users would be able to do via clicking can be done through a command menu.

With macOS's Spotlight and Linear's command + k experience in mind, kbar aims to be a simple abstraction to add a fast and extensible command + k menu to your site.

Features

  • Built in animations and fully customizable components
  • Keyboard navigation support; e.g. control + n or control + p for the navigation wizards
  • Keyboard shortcuts support for registering keystrokes to specific actions; e.g. hit t for Twitter, hit ? to immediate bring up documentation search
  • Nested actions enable creation of rich navigation experiences; e.g. hit backspace to navigate to the previous action
  • Performance as a first class priority; tens of thousands of actions? No problem.
  • History management; easily add undo and redo to each action
  • Built in screen reader support
  • A simple data structure which enables anyone to easily build their own custom components

Usage

Have a fully functioning command menu for your site in minutes. First, install kbar.

npm install kbar

There is a single provider which you will wrap your app around; you do not have to wrap your entire app; however, there are no performance implications by doing so.

// app.tsx
import { KBarProvider } from "kbar";

function MyApp() {
  return (
    <KBarProvider>
      // ...
    </KBarProvider>
  );
}

Let's add a few default actions. Actions are the core of kbar – an action define what to execute when a user selects it.

  const actions = [
    {
      id: "blog",
      name: "Blog",
      shortcut: ["b"],
      keywords: "writing words",
      perform: () => (window.location.pathname = "blog"),
    },
    {
      id: "contact",
      name: "Contact",
      shortcut: ["c"],
      keywords: "email",
      perform: () => (window.location.pathname = "contact"),
    },
  ]

  return (
    <KBarProvider actions={actions}>
      // ...
    </KBarProvider>
  );
}

Next, we will pull in the provided UI components from kbar:

// app.tsx
import {
  KBarProvider,
  KBarPortal,
  KBarPositioner,
  KBarAnimator,
  KBarSearch,
  useMatches,
  NO_GROUP
} from "kbar";

// ...
  return (
    <KBarProvider actions={actions}>
      <KBarPortal> // Renders the content outside the root node
        <KBarPositioner> // Centers the content
          <KBarAnimator> // Handles the show/hide and height animations
            <KBarSearch /> // Search input
          </KBarAnimator>
        </KBarPositioner>
      </KBarPortal>
      <MyApp />
    </KBarProvider>;
  );
}

At this point hitting cmd+k (macOS) or ctrl+k (Linux/Windows) will animate in a search input and nothing more.

kbar provides a few utilities to render a performant list of search results.

  • useMatches at its core returns a flattened list of results and group name based on the current search query; i.e. ["Section name", Action, Action, "Another section name", Action, Action]
  • KBarResults renders a performant virtualized list of these results

Combine the two utilities to create a powerful search interface:

import {
  // ...
  KBarResults,
  useMatches,
  NO_GROUP,
} from "kbar";

// ...
// <KBarAnimator>
//   <KBarSearch />
<RenderResults />;
// ...

function RenderResults() {
  const { results } = useMatches();

  return (
    <KBarResults
      items={results}
      onRender={({ item, active }) =>
        typeof item === "string" ? (
          <div>{item}</div>
        ) : (
          <div
            style={{
              background: active ? "#eee" : "transparent",
            }}
          >
            {item.name}
          </div>
        )
      }
    />
  );
}

Hit cmd+k (macOS) or ctrl+k (Linux/Windows) and you should see a primitive command menu. kbar allows you to have full control over all aspects of your command menu – refer to the docs to get an understanding of further capabilities. Looking forward to see what you build.

Used by

Listed are some of the various usages of kbar in the wild – check them out! Create a PR to add your site below.

Contributing to kbar

Contributions are welcome!

New features

Please open a new issue so we can discuss prior to moving forward.

Bug fixes

Please open a new Pull Request for the given bug fix.

Nits and spelling mistakes

Please open a new issue for things like spelling mistakes and README tweaks – we will group the issues together and tackle them as a group. Please do not create a PR for it!

Download Details:

Author: timc1
Source Code: https://github.com/timc1/kbar 
License: MIT license

#typescript #javascript #react #hooks 

Kbar: Fast, Portable, and Extensible Cmd+k interface for Your Site
Lawrence  Lesch

Lawrence Lesch

1672147140

Wagmi: React Hooks for Ethereum

Wagmi

React Hooks for Ethereum

Features

  • 🚀 20+ hooks for working with wallets, ENS, contracts, transactions, signing, etc.
  • 💼 Built-in wallet connectors for MetaMask, WalletConnect, Coinbase Wallet, and Injected
  • 👟 Caching, request deduplication, multicall, batching, and persistence
  • 🌀 Auto-refresh data on wallet, block, and network changes
  • 🦄 TypeScript ready (infer types from ABIs and EIP-712 Typed Data)
  • 🌳 Test suite running against forked Ethereum network

...and a lot more.

Installation

Install wagmi and its ethers peer dependency.

npm install wagmi ethers

Quick Start

Connect a wallet in under 60 seconds. LFG.

import { WagmiConfig, createClient } from 'wagmi'
import { getDefaultProvider } from 'ethers'

const client = createClient({
  autoConnect: true,
  provider: getDefaultProvider(),
})

function App() {
  return (
    <WagmiConfig client={client}>
      <Profile />
    </WagmiConfig>
  )
}
import { useAccount, useConnect, useDisconnect } from 'wagmi'
import { InjectedConnector } from 'wagmi/connectors/injected'

function Profile() {
  const { address } = useAccount()
  const { connect } = useConnect({
    connector: new InjectedConnector(),
  })
  const { disconnect } = useDisconnect()

  if (address)
    return (
      <div>
        Connected to {address}
        <button onClick={() => disconnect()}>Disconnect</button>
      </div>
    )
  return <button onClick={() => connect()}>Connect Wallet</button>
}

In this example, we create a wagmi Client and pass it to the WagmiConfig React Context. The client is set up to use the ethers Default Provider and automatically connect to previously connected wallets.

Next, we use the useConnect hook to connect an injected wallet (e.g. MetaMask) to the app. Finally, we show the connected account's address with useAccount and allow them to disconnect with useDisconnect.

We've only scratched the surface for what you can do with wagmi!

Check out ConnectKit or Web3Modal to get started with pre-built interface on top of wagmi for managing wallet connections.

Documentation

For full documentation and examples, visit wagmi.sh.

Community

Check out the following places for more wagmi-related content:

Support

If you find wagmi useful, please consider supporting development. Thank you 🙏

Contributing

If you're interested in contributing, please read the contributing docs before submitting a pull request.

Download Details:

Author: Wagmi-dev
Source Code: https://github.com/wagmi-dev/wagmi 
License: MIT license

#typescript #react #hooks #ethereum 

Wagmi: React Hooks for Ethereum
Monty  Boehm

Monty Boehm

1671439620

How to Working With React Hooks and TypeScript

Hooks have already changed the way we use React for the better. Add TypeScript to the mix, and developers can leverage static typing and type transformations to reduce the noise of many interface descriptions. In this article, Toptal Software Developer Nicolas Zozol demonstrates hooks and TypeScript in React and explains how this powerful combo can save time and streamline your code.

Hooks were introduced to React in February 2019 to improve code readability. We already discussed React hooks in previous articles, but this time we are examining how hooks work with TypeScript.

Prior to hooks, React components used to have two flavors:

  • Classes that handle a state
  • Functions that are fully defined by their props

A natural use of these was to build complex container components with classes and simple presentational components with pure functions.

What Are React Hooks?

Container components handle state management and requests to the server, which will be then called in this article side effects. The state will be propagated to the container children through the props.

 

What Are React Hooks?

 

But as the code grows, functional components tend to be transformed as container components.

Upgrading a functional component into a smarter one is not that painful, but it is a time-consuming and unpleasant task. Moreover, distinguishing presenters and containers very strictly is not welcome anymore.

Hooks can do both, so the resulting code is more uniform and has almost all the benefits. Here is an example of adding a local state to a small component that is handling a quotation signature.

// put signature in local state and toggle signature when signed changes
function QuotationSignature({quotation}) {

   const [signed, setSigned] = useState(quotation.signed);
   useEffect(() => {
       fetchPost(`quotation/${quotation.number}/sign`)
   }, [signed]); // effect will be fired when signed changes

   return <>
       <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/>
       Signature
   </>
}

There is a big bonus to this—coding with TypeScript was great with Angular but bloated with React. However, coding React hooks with TypeScript is a pleasant experience.

TypeScript with Old React

TypeScript was designed by Microsoft and followed the Angular path when React developed Flow, which is now losing traction. Writing React classes with naive TypeScript was quite painful because React developers had to type both props and state even though many keys were the same.

Here is a simple domain object. We make a quotation app, with a Quotation type, managed in some crud components with state and props. The Quotation can be created, and its associated status can change to signed or not.

interface Quotation{
   id: number
   title:string;
   lines:QuotationLine[]
   price: number
}

interface QuotationState{
   readonly quotation:Quotation;
   signed: boolean
}

interface QuotationProps{
   quotation:Quotation;
}

class QuotationPage extends Component<QuotationProps, QuotationState> {
	 // ...
}

But imagine the QuotationPage will now ask the server for an id: for example, the 678th quotation made by the company. Well, that means the QuotationProps does not know that important number—it’s not wrapping a Quotation exactly. We have to declare much more code in the QuotationProps interface:

interface QuotationProps{
   // ... all the attributes of Quotation but id
   title:string;
   lines:QuotationLine[]
   price: number
}

We copy all attributes but the id in a new type. Hm. That makes me think of old Java, which involved writing a bunch of DTOs. To overcome that, we will increase our TypeScript knowledge to bypass the pain.

Benefits of TypeScript with Hooks

By using hooks, we will be able to get rid of the previous QuotationState interface. To do this, we will split QuotationState into two different parts of the state.

interface QuotationProps{
   quotation:Quotation;
}
function QuotationPage({quotation}:QuotationProps){
   const [quotation, setQuotation] = useState(quotation);
   const [signed, setSigned] = useState(false);
}

By splitting the state, we don’t have to create new interfaces. Local state types are often inferred by the default state values.

Components with hooks are all functions. So, we can write the same component returning the FC<P> type defined in the React library. The function explicitly declares its return type, setting along the props type.

const QuotationPage : FC<QuotationProps> = ({quotation}) => {
   const [quotation, setQuotation] = useState(quotation);
   const [signed, setSigned] = useState(false);
}

Evidently, using TypeScript with React hooks is easier than using it with React classes. And because strong typing is a valuable security for code safety, you should consider using TypeScript if your new project uses hooks. You should definitely use hooks if you want some TypeScript.

There are numerous reasons why you could avoid TypeScript, using React or not. But if you do choose to use it, you should definitely use hooks, too.

Specific Features of TypeScript Suitable for Hooks

In the previous React hooks TypeScript example, I still have the number attribute in the QuotationProps, but there is yet no clue of what that number actually is.

TypeScript gives us a long list of utility types, and three of them will help us with React by reducing the noise of many interface descriptions.

  • Partial<T> : any sub keys of T
  • Omit<T, 'x'> : all keys of T except the key x
  • Pick<T, 'x', 'y', 'z'> : Exactly x, y, z keys from T

 

Specific Features of TypeScript Suitable for Hooks

 

In our case, we would like Omit<Quotation, 'id'> to omit the id of the quotation. We can create a new type on the fly with the type keyword.

Partial<T> and Omit<T> does not exist in most typed languages such as Java but helps a lot for examples with Forms in front-end development. It simplifies the burden of typing.

type QuotationProps= Omit<Quotation, id>;
function QuotationPage({quotation}:QuotationProps){
   const [quotation, setQuotation] = useState(quotation);
   const [signed, setSigned] = useState(false);
   // ...
}

Now we have a Quotation with no id. So, maybe we could design a Quotation and PersistedQuotation extends Quotation. Also, we will easily resolve some recurrent if or undefined problems. Should we still call the variable quotation, though it’s not the full object? That’s beyond the scope of this article, but we will mention it later on anyway.

However, now we are sure that we will not spread an object we thought had a number. Using Partial<T> does not bring all these guarantees, so use it with discretion.

Pick<T, ‘x’|’y’> is another way to declare a type on the fly without the burden of having to declare a new interface. If a component, simply edit the Quotation title:

type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>

Or just:

function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}

Don’t judge me, I am a big fan of Domain Driven Design. I’m not lazy to the point that I don’t want to write two more lines for a new interface. I use interfaces for precisely describing the Domain names, and these utility functions for local code correctness, avoiding noise. The reader will know that Quotation is the canonical interface.

Other Benefits of React Hooks

The React team always viewed and treated React as a functional framework. They used classes so that a component could handle its own state, and now hooks as a technique allowing a function to keep track of the component state.

interface Place{
  city:string,
  country:string
}
const initialState:Place = {
  city: 'Rosebud',
  country: 'USA'
};
function reducer(state:Place, action):Partial<Place> {
  switch (action.type) {
    case 'city':
      return { city: action.payload };
    case 'country':
      return { country: action.payload };
  }
}
function PlaceForm() {
  const [state, dispatch] = useReducer(reducer, initialState);
return (
    <form>
      <input type="text" name="city"  onChange={(event) => {
          dispatch({ type: 'city',payload: event.target.value})
        }} 
        value={state.city} />
      <input  type="text"  name="country"   onChange={(event) => {
          dispatch({type: 'country', payload: event.target.value })
        }}
 
        value={state.country} />
   </form>
  );
}

Here is a case where using Partial is safe and a good choice.

Though a function can be executed numerous times, the associated useReducer hook will be created only once.

By naturally extracting the reducer function out of the component, the code can be divided into multiple independent functions instead of multiple functions inside a class, all linked to the state inside the class.

This is clearly better for testability—some functions deal with JSX, others with behavior, others with business logic, and so on.

You (almost) don’t need Higher Order Components anymore. The Render props pattern is easier to write with functions.

So, reading the code is easier. Your code is not a flow of classes/functions/patterns but a flow of functions. However, because your functions are not attached to an object, it may be difficult to name all these functions.

TypeScript Is Still JavaScript

JavaScript is fun because you can tear your code in any direction. With TypeScript, you can still use keyof to play with the keys of objects. You can use type unions to create something unreadable and unmaintainable - no, I don’t like those. You can use type alias to pretend a string is a UUID.

But you can do it with null safety. Be sure your tsconfig.json has the "strict":true option. Check it before the start of the project, or you will have to refactor almost every line!

There are debates on the level of typing you put in your code. You can type everything or let the compiler infer the types. It depends on the linter configuration and team choices.

Also, you can still make runtime errors! TypeScript is simpler than Java and avoids covariance/contravariance troubles with Generics.

In this Animal/Cat example, we have an Animal list that is identical to the Cat list. Unfortunately, it’s a contract in the first line, not the second. Then, we add a duck to the Animal list, so the list of Cat is false.

interface Animal {}
 
interface Cat extends Animal {
  meow: () => string;
}
 
const duck = {age: 7};
const felix = {
  age: 12,
  meow: () => "Meow"
};
 
const listOfAnimals: Animal[] = [duck];
const listOfCats: Cat[] = [felix];
 
 
function MyApp() {
  
  const [cats , setCats] = useState<Cat[]>(listOfCats);
  // Here the thing:  listOfCats is declared as a Animal[]
  const [animals , setAnimals] = useState<Animal[]>(listOfCats)
  const [animal , setAnimal] = useState(duck)
 
  return <div onClick={()=>{
    animals.unshift(animal) // we set as first cat a duck !
    setAnimals([...animals]) // dirty forceUpdate
    }
    }>
    The first cat says {cats[0].meow()}</div>;
}

TypeScript has only a bivariant approach for generics that is simple and helps JavaScript developer adoption. If you name your variables correctly, you will rarely add a duck to a listOfCats.

Also, there is a proposal for adding in and out contracts for covariance and contravariance.

Conclusion

I recently came back to Kotlin, which is a good, typed language, and it was complicated to type complex generics correctly. You don’t have that generic complexity with TypeScript because of the simplicity of duck typing and bivariant approach, but you have other problems. Perhaps you didn’t encounter a lot of problems with Angular, designed with and for TypeScript, but you had them with React classes.

TypeScript was probably the big winner of 2019. It gained React but is also conquering the back-end world with Node.js and its ability to type old libraries with quite simple declaration files. It’s burying Flow, though some go all the way with ReasonML.

Now, I feel that hooks+TypeScript is more pleasant and productive than Angular. I wouldn’t have thought that six months ago.

Original article source at: https://www.toptal.com/

#typescript #react #hooks 

How to Working With React Hooks and TypeScript
Gordon  Matlala

Gordon Matlala

1671194580

Complete Guide to Testing React Hooks

A relatively recent addition to React, hooks have already changed React development for the better through improved code readability and state management. But how do we test them? In this article, Toptal React Developer Avi Aryan outlines why it is crucial to test hooks and introduces us to his React Hooks testing routine.

Hooks were introduced in React 16.8 in late 2018. They are functions that hook into a functional component and allow us to use state and component features like componentDidUpdate, componentDidMount, and more. This was not possible before.

Also, hooks allow us to reuse component and state logic across different components. This was tricky to do before. Therefore, hooks have been a game-changer.

In this article, we will explore how to test React Hooks. We will pick a sufficiently complex hook and work on testing it.

We expect that you are an avid React developer already familiar with React Hooks. In case you want to brush up your knowledge, you should check out our tutorial, and here’s the link to the official documentation.

The Hook We Will Use for Testing

For this article, we will use a hook that I wrote in my previous article, Stale-while-revalidate Data Fetching with React Hooks. The hook is called useStaleRefresh. If you haven’t read the article, don’t worry as I will recap that part here.

This is the hook we will be testing:

import { useState, useEffect } from "react";
const CACHE = {};

export default function useStaleRefresh(url, defaultValue = []) {
  const [data, setData] = useState(defaultValue);
  const [isLoading, setLoading] = useState(true);

  useEffect(() => {
    // cacheID is how a cache is identified against a unique request
    const cacheID = url;
    // look in cache and set response if present
    if (CACHE[cacheID] !== undefined) {
      setData(CACHE[cacheID]);
      setLoading(false);
    } else {
      // else make sure loading set to true
      setLoading(true);
      setData(defaultValue);
    }
    // fetch new data
    fetch(url)
      .then((res) => res.json())
      .then((newData) => {
        CACHE[cacheID] = newData;
        setData(newData);
        setLoading(false);
      });
  }, [url, defaultValue]);

  return [data, isLoading];
}

As you can see, useStaleRefresh is a hook that helps fetch data from a URL while returning a cached version of the data, if it exists. It uses a simple in-memory store to hold the cache.

It also returns an isLoading value that is true if no data or cache is available yet. The client can use it to show a loading indicator. The isLoading value is set to false when cache or fresh response is available.

 

A flowchart tracking the stale-while-refresh logic

 

At this point, I will suggest you spend some time reading the above hook to get a complete understanding of what it does.

In this article, we will see how we can test this hook, first using no test libraries (only React Test Utilities and Jest) and then by using react-hooks-testing-library.

The motivation behind using no test libraries, i.e., only a test runner Jest, is to demonstrate how testing a hook works. With that knowledge, you will be able to debug any issues that may arise when using a library that provides testing abstraction.

Defining the Test Cases

Before we begin testing this hook, let’s come up with a plan of what we want to test. Since we know what the hook is supposed to do, here’s my eight-step plan for testing it:

  1. When the hook is mounted with URL url1, isLoading is true and data is defaultValue.
  2. After an asynchronous fetch request, the hook is updated with data data1 and isLoading is false.
  3. When the URL is changed to url2, isLoading becomes true again and data is defaultValue.
  4. After an asynchronous fetch request, the hook is updated with new data data2.
  5. Then, we change the URL back to url1. The data data1 is instantly received since it is cached. isLoading is false.
  6. After an asynchronous fetch request, when a fresh response is received, the data is updated to data3.
  7. Then, we change the URL back to url2. The data data2 is instantly received since it is cached. isLoading is false.
  8. After an asynchronous fetch request, when a fresh response is received, the data is updated to data4.

The test flow mentioned above clearly defines the trajectory of how the hook will function. Therefore, if we can ensure this test works, we are good.

 

Test flow

 

Testing Hooks Without a Library

In this section, we will see how to test hooks without using any libraries. This will provide us with an in-depth understanding of how to test React Hooks.

To begin this test, first, we would like to mock fetch. This is so we can have control over what the API returns. Here is the mocked fetch.

function fetchMock(url, suffix = "") {
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve({
        json: () =>
          Promise.resolve({
            data: url + suffix,
          }),
      });
    }, 200 + Math.random() * 300)
  );
}

This modified fetch assumes that the response type is always JSON and it, by default, returns the parameter url as the data value. It also adds a random delay of between 200ms and 500ms to the response.

If we want to change the response, we simply set the second argument suffix to a non-empty string value.

At this point, you might ask, why the delay? Why don’t we just return the response instantly? This is because we want to replicate the real world as much as possible. We can’t test the hook correctly if we return it instantly. Sure, we can reduce the delay to 50-100ms for faster tests, but let’s not worry about that in this article.

With our fetch mock ready, we can set it to the fetch function. We use beforeAll and afterAll for doing so because this function is stateless so we don’t need to reset it after an individual test.

// runs before any tests start running
beforeAll(() => {
  jest.spyOn(global, "fetch").mockImplementation(fetchMock);
});

// runs after all tests have finished
afterAll(() => {
  global.fetch.mockClear();
});

Then, we need to mount the hook in a component. Why? Because hooks are just functions on their own. Only when used in components can they respond to useState, useEffect, etc.

So, we need to create a TestComponent that helps us mount our hook.

// defaultValue is a global variable to avoid changing the object pointer on re-render
// we can also deep compare `defaultValue` inside the hook's useEffect
const defaultValue = { data: "" };

function TestComponent({ url }) {
  const [data, isLoading] = useStaleRefresh(url, defaultValue);
  if (isLoading) {
    return <div>loading</div>;
  }
  return <div>{data.data}</div>;
}

This is a simple component that either renders the data or renders a “Loading” text prompt if data is loading (being fetched).

Once we have the test component, we need to mount it on the DOM. We use beforeEach and afterEach to mount and unmount our component for each test because we want to start with a fresh DOM before each test.

let container = null;

beforeEach(() => {
  // set up a DOM element as a render target
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  // cleanup on exiting
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

Notice that container has to be a global variable since we want to have access to it for test assertions.

With that set, let’s do our first test where we render a URL url1, and since fetching the URL will take some time (see fetchMock), it should render “loading” text initially.

it("useStaleRefresh hook runs correctly", () => {
  act(() => {
    render(<TestComponent url="url1" />, container);
  });
  expect(container.textContent).toBe("loading");
})

Run the test using yarn test, and it works as expected. Here’s the complete code on GitHub.

Now, let’s test when this loading text changes to the fetched response data, url1.

How do we do that? If you look at fetchMock, you see we wait for 200-500 milliseconds. What if we put a sleep in the test that waits for 500 milliseconds? It will cover all possible wait times. Let’s try that.

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

it("useStaleRefresh hook runs correctly", async () => {
  act(() => {
    render(<TestComponent url="url1" />, container);
  });
  expect(container.textContent).toBe("loading");

  await sleep(500);
  expect(container.textContent).toBe("url1");
});

The test passes, but we see an error as well (code).

 PASS  src/useStaleRefresh.test.js
  ✓ useStaleRefresh hook runs correctly (519ms)

  console.error node_modules/react-dom/cjs/react-dom.development.js:88
    Warning: An update to TestComponent inside a test was not wrapped in act(...).

This is because the state update in useStaleRefresh hook happens outside act(). To make sure DOM updates are processed timely, React recommends you use act() around every time a re-render or UI update might happen. So, we need to wrap our sleep with act as this is the time the state update happens. After doing so, the error goes away.

import { act } from "react-dom/test-utils";
// ...
await act(() => sleep(500));

Now, run it again (code on GitHub). As expected, it passes without errors.

Let’s test the next situation where we first change the URL to url2, then check the loading screen, then wait for fetch response, and finally check the url2 text. Since we now know how to correctly wait for async changes, this should be easy.

act(() => {
  render(<TestComponent url="url2" />, container);
});
expect(container.textContent).toContain("loading");

await act(() => sleep(500));
expect(container.textContent).toBe("url2");

Run this test, and it passes as well. Now, we can also test the case where response data changes and the cache comes into play.

You will notice that we have an additional argument suffix in our fetchMock function. This is for changing the response data. So we update our fetch mock to use the suffix.

global.fetch.mockImplementation((url) => fetchMock(url, "__"));

Now, we can test the case where the URL is set to url1 again. It first loads url1 and then url1__. We can do the same for url2, and there should be no surprises.

it("useStaleRefresh hook runs correctly", async () => {
  // ...
  // new response
  global.fetch.mockImplementation((url) => fetchMock(url, "__"));

  // set url to url1 again
  act(() => {
    render(<TestComponent url="url1" />, container);
  });
  expect(container.textContent).toBe("url1");
  await act(() => sleep(500));
  expect(container.textContent).toBe("url1__");

  // set url to url2 again
  act(() => {
    render(<TestComponent url="url2" />, container);
  });
  expect(container.textContent).toBe("url2");
  await act(() => sleep(500));
  expect(container.textContent).toBe("url2__");
});

This entire test gives us the confidence that the hook does indeed work as expected (code). Hurray! Now, let’s take a quick look at optimization using helper methods.

Optimizing Testing by Using Helper Methods

So far, we have seen how to completely test our hook. The approach is not perfect but it works. And yet, can we do better?

Yes. Notice that we are waiting for a fixed 500ms for each fetch to be completed, but each request takes anything from 200 to 500ms. So, we are clearly wasting time here. We can handle this better by just waiting for the time each request takes.

How do we do that? A simple technique is executing the assertion until it passes or a timeout is reached. Let’s create a waitFor function that does that.

async function waitFor(cb, timeout = 500) {
  const step = 10;
  let timeSpent = 0;
  let timedOut = false;

  while (true) {
    try {
      await sleep(step);
      timeSpent += step;
      cb();
      break;
    } catch {}
    if (timeSpent >= timeout) {
      timedOut = true;
      break;
    }
  }

  if (timedOut) {
    throw new Error("timeout");
  }
}

This function simply runs a callback (cb) inside a try...catch block every 10ms, and if the timeout is reached, it throws an error. This allows us to run an assertion until it passes in a safe manner (i.e., no infinite loops).

We can use it in our test as follows: Instead of sleeping for 500ms and then asserting, we use our waitFor function.

// INSTEAD OF 
await act(() => sleep(500));
expect(container.textContent).toBe("url1");
// WE DO
await act(() =>
  waitFor(() => {
    expect(container.textContent).toBe("url1");
  })
);

Do it in all such assertions, and we can see a considerable difference in how fast our test runs (code).

Now, all this is great, but maybe we don’t want to test the hook via UI. Maybe we want to test a hook using its return values. How do we do that?

It won’t be difficult because we already have access to our hook’s return values. They are just inside the component. If we can take those variables out to the global scope, it will work. So let’s do that.

Since we will be testing our hook via its return value and not rendered DOM, we can remove the HTML render from our component and make it render null. We should also remove the destructuring in hook’s return to make it more generic. Thus, we have this updated test component.

// global variable
let result;

function TestComponent({ url }) {
  result = useStaleRefresh(url, defaultValue);
  return null;
}

Now the hook’s return value is stored in result, a global variable. We can query it for our assertions.

// INSTEAD OF
expect(container.textContent).toContain("loading");
// WE DO
expect(result[1]).toBe(true);

// INSTEAD OF 
expect(container.textContent).toBe("url1");
// WE DO
expect(result[0].data).toBe("url1");

After we change it everywhere, we can see our tests are passing (code).

At this point, we get the gist of testing React Hooks. There are a few improvements we can still make, such as:

  1. Moving result variable to a local scope
  2. Removing the need to create a component for every hook we want to test

We can do it by creating a factory function that has a test component inside it. It should also render the hook in the test component and give us access to the result variable. Let’s see how we can do that.

First, we move TestComponent and result inside the function. We will also need to pass Hook and the Hook arguments as function’s arguments so that they can be used in our test component. Using that, here’s what we have. We are calling this function renderHook.

function renderHook(hook, args) {
  let result = {};

  function TestComponent({ hookArgs }) {
    result.current = hook(...hookArgs);
    return null;
  }

  act(() => {
    render(<TestComponent hookArgs={args} />, container);
  });

  return result;
}

The reason we have result as an object that stores data in result.current is because we want the return values to be updated as the test runs. The return value of our hook is an array, so it would have been copied by value if we returned it directly. By storing it in an object, we return a reference to that object so the return values can be updated by updating result.current.

Now, how do we go about updating the hook? Since we are already using a closure, let’s enclose another function rerender that can do that.

The final renderHook function looks like this:

function renderHook(hook, args) {
  let result = {};

  function TestComponent({ hookArgs }) {
    result.current = hook(...hookArgs);
    return null;
  }

  function rerender(args) {
    act(() => {
      render(<TestComponent hookArgs={args} />, container);
    });
  }

  rerender(args);
  return { result, rerender };
}

Now, we can use it in our test. Instead of using act and render, we do the following:

const { rerender, result } = renderHook(useStaleRefresh, [
  "url1",
  defaultValue,
]);

Then, we can assert using result.current and update the hook using rerender. Here’s a simple example:

rerender(["url2", defaultValue]);
expect(result.current[1]).toBe(true); // check isLoading is true

Once you change it in all places, you will see it works without any problems (code).

Brilliant! Now we have a much cleaner abstraction to test hooks. We can still do better - for example, defaultValue needs to be passed every time to rerender even though it doesn’t change. We can fix that.

But let’s not beat around the bush too much as we already have a library that improves this experience significantly.

Enter react-hooks-testing-library.

Testing Using React-hooks-testing-library

React-hooks-testing-library does everything we have talked about before and then some. For example, it handles container mounting and unmounting so you don’t have to do that in your test file. This allows us to focus on testing our hooks without getting distracted.

It comes with a renderHook function that returns rerender and result. It also returns wait, which is similar to waitFor, so you don’t have to implement it yourself.

Here is how we render a hook in React-hooks-testing-library. Notice the hook is passed in the form of a callback. This callback is run every time the test component re-renders.

const { result, wait, rerender } = renderHook(
  ({ url }) => useStaleRefresh(url, defaultValue),
  {
    initialProps: {
      url: "url1",
    },
  }
);

Then, we can test if the first render resulted in isLoading as true and return value as defaultValue by doing this. Exactly similar to what we implemented above.

expect(result.current[0]).toEqual(defaultValue);
expect(result.current[1]).toBe(true);

To test for async updates, we can use the wait method that renderHook returned. It comes wrapped with act() so we don’t need to wrap act() around it.

await wait(() => {
  expect(result.current[0].data).toEqual("url1");
});
expect(result.current[1]).toBe(false);

Then, we can use rerender to update it with new props. Notice we don’t need to pass defaultValue here.

rerender({ url: "url2" });

Finally, the rest of the test will proceed similarly (code).

Wrapping Up

My aim was to show you how to test React Hooks by taking an example of an async hook. I hope this helps you confidently tackle the testing of any kind of hook, as the same approach should apply to most of them.

I would recommend you use React-hooks-testing-library since it’s complete, and I haven’t run into significant problems with it thus far. In case you do encounter a problem, you now know how to approach it using the intricacies of testing hooks described in this article.

Original article source at: https://www.toptal.com/

#react #hooks #testing 

Complete Guide to Testing React Hooks