1.3.3 • Published 3 years ago

react-pieceful-state v1.3.3

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

React Pieceful State

A ridiculously tiny library for global and regional state management in React


React Pieceful State is a tiny, but straight-forward mechanism for state management in React. It offers management of global and regional state with the ease of simply calling a hook on the spot, wherever and whenever you need it.

Some of the benefits it offers:

  • Zero boilerplate or in-advance global state setup necessary - it's entirely optional and "decentralized" to your components and hooks
  • State data you no longer use will automatically never be even created in your app
  • So-called "regional state" support
  • It encourages having your state split into smaller pieces
  • Makes it easy to search through your code and detect what piece of state is no longer used and can safely be deleted with no "leftovers"

Overview

Installation

npm install react-pieceful-state

or

yarn add react-pieceful-state

Usage

There are four React Pieceful State exports at your disposal:

import {
  PiecefulProvider,
  createStatePiece,
  useStatePiece,
  withPiecefulState,
} from 'react-pieceful-state';

PiecefulProvider

First you need to wrap your app with the PiecefulProvider component. No props are required at this point.

There is also an optional string prop called region, which is explained in the Regional state section

import { PiecefulProvider } from 'react-pieceful-state';

const App = () => {
  return (
    <PiecefulProvider>
      <Main />
    </PiecefulProvider>
  );
};

createStatePiece

Then you need to declare a "piece of state" in the same file with any component that's going to need it. It returns a hook that can be used for accessing and modifying that state by that component, or any other component for that matter. There are two arguments it requires:

  • base string - this will be the unique identifier of the "piece of state" we're creating
  • defaultValue any - its default value (not to confuse it with initial value)

There is also a third optional string argument called region, which is explained in the Regional state section

const useUserData = createStatePiece('user-data', {
  name: '',
  email: '',
});

const SomeComponent = () => {
  ...
};

Since the state it represents is going to be global for the entire tree wrapped by PiecefulProvider, the returned hook can be called from anywhere inside that tree. It returns an array of three elements as shown below:

import useUserData from 'your-path-to-the/useUserData';

const AnyComponentOrHook = () => {
  const [currentStateValue, setStateValue, resetStateValue] = useUserData();
  // or destructure it directly: const [{ name, email }, setStateValue, resetStateValue] = useUserData();
};

setStateValue is a state setter function that only requires a single reducer function as an argument:

setStateValue((state) => ({
  ...state,
  name: 'John Smith',
}));

resetStateValue is a function that resets the state to its default value. It accepts an optional reducer function as an argument which lets you combine the default value with some non-default value of your own choosing.

// sets the state to its default value
resetStateValue();

// also sets the state to its default value
resetStateValue((defaultState) => ({ ...defaultState }));

// sets the state to its default value combined with your own specific modifications
resetStateValue((defaultState) => ({
  ...defaultState,
  name: '[name was deleted]',
}));

Let's imagine that by the time your component gets rendered you've already got some initial data you want to populate your state with, which can very possibly be different than its default value. You can overwrite the default value simply by passing your preferred initial value as an argument to the hook we generated with createStatePiece:

const [stateValue, setStateValue, resetStateValue] = useUserData({
  name: 'John Cleese',
  email: 'johncleese@mail.com',
});

useStatePiece

In fact, you don't even have to generate your state hooks with createStatePiece. You can do the exact same thing with useStatePiece in runtime from your component's body. It accepts the same two mandatory arguments as the former: base and default value, which in this case will serve as initial value as well.

Again, there is the third, optional string argument called region, which is explained in the Regional state section

const [{ name, email }, setStateValue, resetStateValue] = useStatePiece(
  'user-data',
  {
    name: someNameInputValue || '',
    email: someEmailInputValue || '',
  }
);

To avoid retyping this hook call along with all of its required arguments across your app, it's recommended that you wrap it in your own custom hook and then reuse that hook, like so:

import { useStatePiece } from 'react-pieceful-state';

const useUserData = (initialName, initialEmail) => {
  const [{ name, email }, setStateValue] = useStatePiece('user-data', {
    name: initialName || '',
    email: initialEmail || '',
  });

  const setName = (name) => {
    setStateValue((state) => ({ ...state, name }));
  };

  const setEmail = (email) => {
    setStateValue((state) => ({ ...state, email }));
  };

  return [
    { name, email },
    { setName, setEmail },
  ];
};

export default useUserData;

withPiecefulState

withPiecefulState is a component wrapper function that makes it possible for class components to use hooks. It accepts two arguments:

  • The class component to be wrapped
  • A function whose only argument are the wrapped component's own props, which may or may not return an unlimited number of hook outputs, which would otherwise be impossible to do from the body of a class component.
class MyClassComponent extends PureComponent {
  constructor(props) {
    super(props);

    // how you would destructure hooks' output
    const {
      userDataHook: [{ name, email }, { setName, setEmail }],
      someOtherHook: [someStateValue, setSomeStateValue],
    } = props;
  }

  onNameChange = ({ target: { value } }) => {
    const {
      userDataHook: [, { setName }],
    } = this.props;

    setName(value);
  };

  onEmailChange = ({ target: { value } }) => {
    const {
      userDataHook: [, { setEmail }],
    } = this.props;

    setEmail(value);
  };

  render() {
    const {
      userDataHook: [{ name, email }],
    } = this.props;

    return (
      <>
        <p>{`Hello ${name} (${email})`}</p>
        <input type="text" name="name" onChange={this.onNameChange} />
        <input type="email" name="email" onChange={this.onEmailChange} />
      </>
    );
  }
}

export default withPiecefulState(MyClassComponent, (ownProps) => {
  // any code can be run here, just as with any hook or function in general
  // including void hook calls, such as useEffect()

  return {
    userDataHook: useUserData(ownProps.initialName, ownProps.initialEmail),
    someOtherHook: useState(false),
  };
});

What is "Regional state"?

So-called "regional state" is state that is neither local, nor global. It only exists in a certain part of your React tree and, unlike typical global state, there can be multiple instances of it, all of which can have their own different values. So it's basically state that is global only to the tree it's on top of.

Example

Consider having something like the following tree

const App = () => (
  <PiecefulProvider>
    <Colors />
  </PiecefulProvider>
);

const Colors = () => {
  const [colors] = useStatePiece('colors', ['red', 'blue', 'white', 'yellow', 'green']);

  return (
    <>
      {colors.map((color) => <ColorfulComponent color={color} />}
    </>
  );
};

const ColorfulComponent = ({ color }) => {
  return (
    <TopComponent>
      <NestedComponent1 mainColor={color}>
        <NestedComponent2 backgroundColor={color}>
          ...............
            ...............
              ...............
                <NestedComponent9 fallbackColor={color}>
                  <NestedComponent10 textColor={color} />
                </NestedComponent9>
              ...............
            ...............
          ...............
        </NestedComponent2>
      </NestedComponent1>
    </TopComponent>
  );
};

And then imagine that each of the NestedComponent1 through NestedComponent10 have their own deeply nested elements and components that might need to use that single value of color. You'd either have to resort to "prop drilling" or to keep as many of those elements and components within the scope of a single component where color exists and then you'd still have to assign each of them with the color value separately, as we're already doing in the example above. Or we would eventually remember about the React Context component.

React's own Context component

Of course, we've got React Context at our disposal. We would typically create a new Context with createContext(...), export it so other components could retrieve its value with useContext(Context), then render it inside a new wrapper component where we would assign it an initial value and craft its mutation logic from scratch.

But wait, why should we have to do all that if we're already using a global state management tool? We shouldn't. And this is where React Pieceful State comes with a unique and convenient solution out of the box.

The region prop of PiecefulProvider and the region argument of createStatePiece and useStatePiece

Both the region prop and the region argument's default value is root. Whenever we use a custom value for any of them, what React Pieceful State is essentially doing under the hood is what was described in the previous section.

Let's go back to the colors.map() statement in ColorfulComponent and wrap each of its outputs with PiecefulProvider, only in this particular instance we give its region value a custom value of "myColor". That's everything React Pieceful State needs to spare us the trouble and create a new "regional" Context for us.

return (
  <>
    {colors.map((color) => (
      <PiecefulProvider region="myColor">
        <Color color={color} />
      </PiecefulProvider>
    )}
  </>
);

Then go back inside ColorfulComponent and make the following modifications:

export const useMyColor = createStatePiece('colorValue', '', 'myColor');

const ColorfulComponent = ({ color }) => {
  const [myColor] = useMyColor(color);
  ...

That's it, now each and every NestedComponent1 through NestedComponent10 that ever needs the color value can be spared from prop drilling and prop assignments with that value. All they need to do is refer to the useMyColor hook we just generated. Our tree now looks something like this:

<TopComponent>
  <NestedComponent1>
    <NestedComponent2>
      ............... ............... ...............
      <NestedComponent9>
        <NestedComponent10 />
      </NestedComponent9>
      ............... ............... ...............
    </NestedComponent2>
  </NestedComponent1>
</TopComponent>
1.3.3

3 years ago

1.3.2

3 years ago

1.3.1

3 years ago

1.3.0

3 years ago