@oxymora/oxymora
TypeScript icon, indicating that this package has built-in type declarations

0.2.3 • Public • Published

Oxymora

Making React components 100% pure.

oxymoron ŏk″sē-môr′ŏn″

noun A rhetorical figure in which incongruous or contradictory terms are combined, as in a 'deafening silence' and a 'mournful optimist'.

Oxymora is the plural of oxymoron, and this library has that name since it has functions named pureStatefulComponent and usePureStatefulCallback, yet "pure stateful" is an oxymoron.

Why Use Oxymora?

Oxymora components are:

  1. Quick to write:
  2. Easy to reason about / debug.
  3. Simple to test.
  4. Always composable.
  5. Quicker to feedback on broken functionality.
  6. Robust.

The Anatomy of an Oxymora Component

Oxymora components start with a TypeScript state-spec; this defines the component's state, input-props and output-props. Here's an example of a state-spec for a Counter component:

type CounterStateSpec = {
  State: number;
  InputProps: {
    incrementBy?: number;
  };
  OutputProps: {
    onCounterChange: number;
  };
};

The props for this component are defined like this:

type CounterProps = Props<CounterStateSpec>;

This is equivalent to manually writing the following:

// NOTE: you don't need to write this because this is what Props<CounterStateSpec> gives you:
type CounterProps = {
  // `InputProps`:
  incrementBy?: number;
  // `OutputProps`:
  onCounterChange?: (number) => void;
  // `State`:
  state: number;
  onStateChange?: (number) => void;
};

Although the State related props are part of the pure-stateful component, they will be removed from the stateful component that's subsequently created using makeStateful. Having the pure-stateful component available to us is helpful for testing however, plus it can also be useful when composition isn't possible using the stateful component.

Here's how you might implement PureStatefulCounter:

export const PureStatefulCounter = pureStatefulComponent<CounterStateSpec>(
  1, // initial state
  ({ state }) => {
    const onIncrementCounter = usePureStatefulCallback<CounterStateSpec>(
      ({ state, incrementBy = 1 }) => {
        const newState = state + incrementBy;

        return {
          state: newState,
          onCounterChange: newState,
        };
      }
    );

    return (
      <Button
        colorScheme="orange"
        leftIcon={<GrFormAdd />}
        onClick={onIncrementCounter}
      >
        {state}
      </Button>
    );
  }
);

Notice the declarative nature of the onIncrementCounter event handler; it signals both a state update and a callback declaratively. Event handlers act on behalf of the closest pure-stateful ancestor component, so this event handler would continue to work even if it was moved into a child component (no callback prop-drilling required).

Finally, the stateful version of the component (i.e. not having state and onStateChange props) can be created like this:

export const StatefulCounter =
  makeStateful<CounterStateSpec>(PureStatefulCounter);

Try a Demo!

We have a live StackBlitz development environment that includes the simple Counter example above, but also an Oxymora implementation of TodoMVC. Use this to play with the components, inspect the code, and test code changes live.

Open in StackBlitz

Readme

Keywords

none

Package Sidebar

Install

npm i @oxymora/oxymora

Weekly Downloads

1

Version

0.2.3

License

MIT

Unpacked Size

32.3 kB

Total Files

6

Last publish

Collaborators

  • dchambers