use-stepper
    TypeScript icon, indicating that this package has built-in type declarations

    2.0.3 • Public • Published

    use-stepper

    React hook to manage a numeric stepper

    npm Version Build Status Code Coverage semantic-release

    The problem

    A numeric stepper (decrement button, input, increment button) is deceptively non-trivial to implement. You have to manage the minimum, the maximum, the input itself displaying the current value (conceptually numeric, but ultimately a string in HTML), allowing a user to type freely and hopefully arrive at a valid number (e.g. "-" is NaN but you have to let them type it so they can get to "-4", so simply parsing their input as a number is insufficient), interpreting the input's value on blur, etc.

    This solution

    This React hook manages all this for you so you only have to worry about the styling. It returns the current value, functions for manual value manipulation, and a collection of prop getters to spread across your form elements to make it all work together. It also includes the ability to provide your own custom state reducer to enable ultimate control over what actions like "increment", "decrement", "coerce", etc. mean in your application.

    Table of Contents

    Installation

    Using yarn:

    yarn add use-stepper

    Or, npm:

    npm install use-stepper

    This package also depends on react 16.8.0 or newer. Please make sure you have that installed as well.

    Usage

    import useStepper from 'use-stepper';
     
    function MyStepper() {
      const {
        getFormProps,
        getInputProps,
        getIncrementProps,
        getDecrementProps,
      } = useStepper();
     
      return (
        <form {...getFormProps()}>
          <button {...getDecrementProps()}>-</button>
          <input {...getInputProps()} />
          <button {...getIncrementProps()}>+</button>
        </form>
      );
    }

    Basic Options

    The hook accepts an options object as its only parameter. The entries for the basic options are as follows:

    defaultValue

    number | optional, default: 0

    The initial value and the value to use as a fallback upon invalid manual input.

    step

    number | optional, default: 1

    The amount by which to increment or decrement the current value.

    min

    number | optional, default: -Number.MAX_VALUE

    The minimum value allowed.

    max

    number | optional, default: Number.MAX_VALUE

    The maximum value allowed.

    Advanced Options

    enableReinitialize

    boolean | optional, default: false

    Controls whether the current value (if unchanged) will update to the new default if defaultValue changes.

    stateReducer

    function(state: object, action: object): object | optional

    Changes to the state of the hook are applied using React's built-in useReducer hook. This function replaces the default reducer implementation, which allows you to author your own logic to execute when an action is dispatched. This gives you ultimate control in the event you wish to constrain the value or otherwise modify the default behavior. It gives you the current state and the action to execute, and you return the new state.

    • state: the current state of the hook
    • action: the action to execute

    State is just an object with a value key (note: value is a string, so it should be converted to a number before performing mathematical operations on it).

    Actions have a type field and an optional payload field. The possible action types are discoverable through useStepper.actionTypes (e.g. useStepper.actionTypes.increment). The default reducer is also available at useStepper.defaultReducer in case you want to defer to the default implementation in some cases (e.g. for the default/unhandled action, perhaps).

    Example

    Constrain the stepper to only use whole numbers:

    function IntegerStepper({ min, max, defaultValue }) {
      function validValueClosestTo(desiredNumericValue) {
        return String(Math.min(max, Math.max(desiredNumericValue, min)));
      }
     
      function integerReducer(state, action) {
        const integerValue = parseInt(state.value, 10);
        switch (action.type) {
          case useStepper.actionTypes.increment: {
            return { value: validValueClosestTo(integerValue + 1) };
          }
          case useStepper.actionTypes.decrement: {
            return { value: validValueClosestTo(integerValue - 1) };
          }
          case useStepper.actionTypes.coerce: {
            if (Number.isNaN(integerValue)) {
              return { value: String(defaultValue) };
            }
            const newValue = validValueClosestTo(integerValue);
            if (newValue !== state.value) {
              return { value: newValue };
            }
            return state;
          }
          default:
            return useStepper.defaultReducer(state, action);
        }
      }
     
      const { getInputProps, getIncrementProps, getDecrementProps } = useStepper({
        min,
        max,
        defaultValue,
        stateReducer: integerReducer,
      });
     
      return (
        <>
          <button {...getDecrementProps()}>-</button>
          <input {...getInputProps()} />
          <button {...getIncrementProps()}>+</button>
        </>
      );
    }

    Return Value

    The hook returns an object with the following shape:

    key type category
    getFormProps function prop getter
    getInputProps function prop getter
    getIncrementProps function prop getter
    getDecrementProps function prop getter
    value string state
    increment function setter
    decrement function setter
    setValue function setter

    prop getters

    These functions are used to apply props to the elements that you render. This gives you maximum flexibility to render what, when, and wherever you like. You call these on the element in question (for example: <input {...getInputProps()})). It's advisable to pass all your props to that function rather than applying them on the element yourself to avoid your props being overridden (or overriding the props returned). For example: getInputProps({ onChange: myChangeHandler }).

    key type description
    getFormProps function({}) returns the props you should apply to the form element for submit handling
    getInputProps function({}) returns the props you should apply to the input element (includes value)
    getIncrementProps function({}) returns the props you should apply to the increment button element
    getDecrementProps function({}) returns the props you should apply to the decrement button element

    state

    These are the values that represent the internal state of the hook.

    key type description
    value string the current value of the stepper

    setters

    These functions are exposed to provide manual manipulation of the internal value.

    key type description
    increment function() increments the value
    decrement function() decrements the value
    setValue function(str) sets the value (note: the argument passed will be coerced to a valid value within the specified range or fall back to the default value if not a valid number)

    Other Solutions

    If you know of any others, please send a pull request to add them here.

    License

    MIT

    Install

    npm i use-stepper

    DownloadsWeekly Downloads

    167

    Version

    2.0.3

    License

    MIT

    Unpacked Size

    77.8 kB

    Total Files

    17

    Last publish

    Collaborators

    • wkovacs64