Skip to content

Latest commit

 

History

History
219 lines (167 loc) · 8.11 KB

Use.md

File metadata and controls

219 lines (167 loc) · 8.11 KB

Use

initInterstate

use-interstate does not require a Context Provider at the root of the components tree. Instead, it is needed to create a unique copy of methods (as the set of unique keys). It provides better security for the whole app. Using Context Providers for sharing the state in popular open-source libraries makes it possible to drain the state through third-party components used as dependencies.

A good practice is to provide the initial state for the app at the very beginning. It is the only way to make sure that any state keys hold valid values at the moment when they are being accessed even for the first time. The argument can be provided to initInterstate for this purpose. Also, in the case of using TypeScript, initializing the app state allows setting cement boundaries for its shape to ensure the safety of use.

import { initInterstate } from 'use-interstate';

const { useInterstate, readInterstate, setInterstate, resetInterstate } = initInterstate({
  // initial state
  color: 'red',
  density: 100,
  integrity: true,
});

useInterstate

useInterstate implies several call interfaces to subscribe for the state changes. The simplest way is to pass the key name as an argument. The second optional parameter repeats the parameter that the standard useState accepts, i. e. the default value in the form of an explicitly defined value or a function being called to calculate it. If the state is already initialized the default value is skipped. If it would be a function it does not even run. Unsettled (not initialized) state keys are expected to hold undefined .

// If the record for "color" was not initialized before then `color === undefined`
const color = useInterstate('color');

// Providing the default state
const integrity = useInterstate('integrity', true);

/**
 * The function runs only once (if the record for "density" was initialized before in another part
 * of the code the function is never called)
 */
const density = useInterstate('density', () => Math.random() * 100);

To subscribe for multiple state keys, useInterstate accepts an array of the key names.

// state1: { color: string; density: number }
const state1 = useInterstate(['color', 'density']);

The way to subscribe and provide the default values for the slice of the state is to pass a raw object containing that default values or a function returning such an object.

// state2: { color: string; density: number; integrity: boolean }
const state2 = useInterstate({ color: '#9e9e9e', density: 100, integrity: true });

// state3: { density: number }
const state3 = useInterstate(() => ({ integrity: false }));

For the function, there is a way how to prevent it to get called every time the component re-renders using an array of deps.

Subscribing to a particular key or several keys makes the component react to every state change affecting them and not to changes of other records of the state. useInterstate also gives an ability to change subscriptions dynamically during the life of the component.

const Component2 = ({ subscribe }) => {
  /**
   * `subscribe` prop could be any key of the state. For example, if it receives the key "color" on
   * the first run, `useInterstate` gives the value of the "color" record. If later the component
   * has "density" for the `subscribe` prop, it re-subscribes and `val` holds the value of the
   * "density" record.
   */
  const val = useInterstate(subscribe);

  return <>{/** ... */}</>;
};

const Component3 = () => <Component2 subscribe={Math.random() < 0.5 ? 'color' : 'density'} />;

The ability to dynamically re-subscribe is not limited to only a single key.

const Component4 = () => {
  const chooseSubscription = Math.random();

  return (
    <Component2
      subscribe={
        chooseSubscription < 0.25
          ? 'color'
          : chooseSubscription < 0.5
          ? ['density', 'integrity']
          : chooseSubscription < 0.75
          ? { color: 'red', density: 10 }
          : () => ({ color: 'blue', integrity: false })
      }
    />
  );
};

readInterstate

readInterstate allows reading the most actual values of the state at any moment when it is needed. The difference between readInterstate and useInterstate is that the former just read the state without an actual subscription. It accepts a single key name or an array of keys.

const color = readInterstate('color');

// state1: { color: string; density: number }
const state1 = readInterstate(['color', 'density']);

acceptSelector method

Both useInterstate and readInterstate have a method acceptSelector that allows reading or subscribing to the state through a selector function. It takes the whole state object as an argument and returns any calculated result based on the records of the state.

const val1 = useInterstate.acceptSelector((state) => (state.integrity ? state.density : null));

The state object inside the selector function is immutable. It is recommended to read necessary properties at the very beginning especially in an asynchronous function. The better way is to destructure properties in the argument.

const val2 = useInterstate.acceptSelector(
  ({ color, density }) => `color is: ${color} and density is: ${density}`
);

The convenient way to make a snapshot of the whole state is:

// Destructuring is required. `(state) => state` returns an unstable immutable object.
const stateSnapshot = readInterstate.acceptSelector(({ ...state }) => state);

useInterstate.acceptSelector can accept an array of deps as a second argument for the sake of performance optimization.

setInterstate

In contrast touseState that returns both the current state value and the setter function providing the way to update the local state, the use-interstate library has an independent method setInterstate created on the stage when the state has been initialized. Any state changes being initiated by thesetInterstate immediately cause re-rendering the components with the subscription to the affected keys of the state. setInterstate has different call interfaces for a single key as well as multiple keys. In both cases setInterstate assumes to take a function that accepts the previous value(s) of the state as an argument to calculate the next state value(s). It is also allowed to provide the new value(s) of the state directly. The new value(s) merge to the current state causing the state update.

setInterstate('color', 'black');

setInterstate('integrity', (prevIntegrity) => !prevIntegrity);

setInterstate({ density: 3, integrity: false });

setInterstate(({ density: prevDensity, integrity: prevIntegrity }) => ({
  density: prevIntegrity ? prevDensity + 1 : 0,
}));

resetInterstate

resetInterstate zeroes the whole state. If the argument is provided it re-initializes the default values for the keys. It is important that if some components were subscribed to the state at the moment when resetInterstate has been called they would not be notified. It is not considered the update of the state. The use cases for 'resetInterstate` are very limited.

resetInterstate();

resetInterstate({color: 'blue', density: 15, integrity: false});

deps

When useInterstate itself and its method acceptSelector have a function as a single argument they would re-subscribe to the state on every re-rendering of the containing component calling this function even if it remains the same. In most cases, it would not affect performance. Anyway, to avoid undesirable extra work they assume an optional second argument which is an array of deps familiar from standard React hooks useEffect, useCallback, useMemo.

const Component5 = ({ choice }) => {
  const state = useInterstate(
    // The function runs when the value of `choice` changes causing re-subscribing
    () => (choice === 0 ? { color: undefined } : { density: undefined }),
    [choice]
  );

  // The function runs only one time on the creation of the component
  const val = useInterstate.acceptSelector(
    ({ color, density }) => `color is: ${color} and density is: ${density}`,
    []
  );

  return <>{/** ... */}</>;
};