This package has been deprecated

Author message:

Package scope has been dropped. Please use preact-graph-state.

@lxsmnsyc/preact-graph-state
TypeScript icon, indicating that this package has built-in type declarations

0.3.0 • Public • Published

@lxsmnsyc/preact-graph-state

Digraph-based state management library for Preact

NPM JavaScript Style Guide

Install

yarn add @lxsmnsyc/preact-graph-state
npm install @lxsmnsyc/preact-graph-state

Usage

What is a Graph State?

A Graph State is a kind of a state management structure that aims to build a container states that behaves like a dependency graph. The structure are made up of graph nodes, which represent an atomic state. Each graph node may depend from another graph node instance. When a graph node state updates, all of its dependents preact immediately from their dependency's new state, and in turn, updates their own state, which can go on until there are no dependents left.

Creating a Graph Node

To create a graph node, you must invoke the createGraphNode function. This function an object with 1 required field and 2 optional fields.

  • get: acts as a default value. If a function is supplied, the function is lazily evaluated as it enters the GraphDomain to provide the default value. This function may also be invoked again if the dependencies update. The function an object with 2 fields:
    • { get, set }: An interface for controlling graph nodes.
      • get(dependencyNode): Registers the dependencyNode as a dependency and returns the dependency's current state. If the dependencyNode updates its state at some point, the created graph node reacts to the state and invokes the get function to recompute and produce the new state.
      • set(newValue): Sets the new state for the graph node.
  • set: Optional. Registers a state mutation side-effect. This function is invoked when the graph node is scheduled for state update. The function receives two parameters:
    • { get, set }: An interface for controlling graph nodes.
      • get(node): Reads the node's value. Unlike get, the node is not treated as a dependency and therefore set won't be invoked when dependencies update.
      • set(node, action): Mutates the node's state. action may be a value or a function that receives the current state of the node and returns the new state, similar to useState's setState.
    • newValue: The new state for the graph node.
  • key: Optional. Uses the provided key instead of a generated key. Provided key may be shared, although get and set functions may be different depending on the node instance passed. Use with caution.
import { createGraphNode } from '@lxsmnsyc/preact-graph-state`;

// A basic node
const basicNode = createGraphNode({
  get: 'Hello',
});

// A dependent node
const dependentNode = createGraphNode({
  get: ({ get }) => {
    const basic = get(basicNode);

    return `${basic}, World!`;
  },
});

// A self-updating node
const timer = createGraphNode({
  get: ({ set }) => {
    let count = 0;
    setInterval(() => {
      count += 1;
      set(count);
    }, 1000);
    return count;
  },
});

const temperatureF = createGraphNode({
  get: 32,
});

const temperatureC = createGraphNode({
  get: ({ get }) => {
    const fahrenheit = get(temperatureF);

    return (fahrenheit - 32) * 5 / 9;
  },
  set: ({ set }, newValue) => {
    set(temperatureF, (newValue * 9) / 5 + 32);
  },
});

Accessing a graph node

Graph nodes, by themselves, are meaningless. They needed a domain to begin computing. <GraphDomain> is a component that defines such domain where all graph nodes live.

import { GraphDomain } from '@lxsmnsyc/preact-graph-state`;

const messageNode = createGraphNode({
  get: 'Hello World',
});

function App() {
  return (
    <GraphDomain>
      {/* children */}
    </GraphDomain>
  );
}

There are also three hooks:

  • useGraphNodeValue: reads a graph node's value. Subscribes to the graph node's state updates.
  • useGraphNodeSetValue: provides a callback that allows graph node's state mutation.
  • useGraphNodeResource: treats the graph node as a valid Preact resource, suspending the component if the graph node's resource is pending.

If one of these hooks are used to access a graph node, that graph node is registered within <GraphDomain> and creates a lifecycle.

Hooks

useGraphNodeValue

This is a Preact hook that reads the graph node's current state and subscribes to further state updates.

function Message() {
  const message = useGraphNodeValue(messageNode);

  return <h1>{ message }</h1>;
}
useGraphNodeSetValue

This is a Preact hook that returns a callback similar to setState that allows state mutation for the given graph node.

function MessageInput() {
  const setMessage = useGraphNodeSetValue(messageNode);

  const onChange = useCallback((e) => {
    setFahrenheit(Number.parseFloat(e.currentTarget.value));
  }, []);

  return (
    <input
      type="text"
      onChange={onChange}
    />
  );
}
useGraphNodeResource

This is a hook that receives a valid graph node resource and suspends the component until the resource is successful. This may resuspend the component if the resource updates itself.

Graph Node Resources

Graph nodes can have synchronous or asynchronous states, but having raw asynchronous states in Preact (aka Promises) can be a bit tedious to handle specially for race conditions. createGraphNodeResource attempts to leverage this problem by turning asynchronous states into reactive Promise results.

/**
 * This is an asynchronous graph node. Trying to
 * access this node using `useGraphNodeValue` just
 * returns a Promise instance. Writing hooks for
 * handling Promise state and side-effects is a
 * tedious task.
 */
const asyncUserNode = createGraphNode(
  get: async ({ get }) => {
    // Get current user id
    const id = get(userIdNode);

    // Fetch user data
    const response = await fetch(`/users/${id}`);

    return response.json();
  },
);

/**
 * Let's transform asyncUserNode into
 * a valid Preact resource node!
 * 
 * This allows us to receive Promise results,
 * preact-graph-state already handles race condition
 * and state updates for us!
 */
const userResourceNode = createGraphNodeResource(asyncUserNode);

Accessing userResourceNode using useGraphNodeValue returns an object which represents the Promise result.

function UserProfile() {
  const result = useGraphNodeValue(userResourceNode);

  if (result.status === 'pending') {
    return <h1>Loading...</h1>;
  }
  if (result.status === 'failure') {
    return <h1>Something went wrong.</h1>;
  }
  return <UserProfileInternal data={result.data} />;
}

There's also another hook called useGraphNodeResource which allows us to suspend the component until the resource is successful.

function UserProfileInternal() {
  const data = useGraphNodeResource(userResourceNode);

  return (
    <UserProfileContainer>
      <UserProfileName>{ data.name }</UserProfileName>
      <UserProfileDescription>{ data.description }</UserProfileDescription>
    </UserProfile>
  );
}

function UserProfile() {
  return (
    <ErrorBoundary fallback={<h1>Something went wrong.</h1>}>
      <Suspense fallback={<h1>Loading...</h1>}>
        <UserProfileInternal />
      </Suspense>
    </ErrorBoundary>
  );
}

Resource-related constructors

fromResource

Converts a graph node resource into a promise-based graph node.

const rawUserDataNode = createGraphNode({
  get: async ({ get }) => fromResource(userResourceNode),
})

waitForAll

Waits for an array of graph node resources to resolve. This is similar in behavior with Promise.all. If one of the resources updates, waitForAll returns to pending state untill all Promise has resolved again.

const resourceA = createGraphNodeResource(
  createGraphNode({
    get: async () => {
      await sleep(1000);
      return 'Message A';
    },
  }),
);
const resourceB = createGraphNodeResource(
  createGraphNode({
    get: async () => {
      await sleep(2000);
      return 'Message B';
    },
  }),
);
const resourceC = createGraphNodeResource(
  createGraphNode({
    get: async () => {
      await sleep(3000);
      return 'Message C';
    },
  }),
);

const values = waitForAll([
  resourceA,
  resourceB,
  resourceC,
]); // ['Message A', 'Message B', 'Message C'] after 3 seconds.

waitForAny

Waits for an array of graph node resources to resolve. This is similar in behavior with Promise.race. If one of the resources updates, waitForAny returns to pending state untill any Promise has resolved again.

const resourceA = createGraphNodeResource(
  createGraphNode({
    get: async () => {
      await sleep(1000);
      return 'Message A';
    },
  }),
);
const resourceB = createGraphNodeResource(
  createGraphNode({
    get: async () => {
      await sleep(2000);
      return 'Message B';
    },
  }),
);
const resourceC = createGraphNodeResource(
  createGraphNode({
    get: async () => {
      await sleep(3000);
      return 'Message C';
    },
  }),
);

const values = waitForAny([
  resourceA,
  resourceB,
  resourceC,
]); // 'Message A'

joinResources

Joins an array of graph node resources into a single graph node that emits an array of resource results.

Factories

Graph node factories allows you to dynamically generate graph nodes of similar logic based on parameters.

Factories has similar option fields for basic graph node creation, but the difference is that these options are higher-order functions instead.

  • key: Optional. Function for generating graph node keys. If not provided, keys are generated from encoding the parameter array into valid JSON string.
  • get: Function for generating graph node values.
  • set: Optional. Function for generating graph node side-effects.
import { createGraphNodeFactory } from '@lxsmnsyc/preact-graph-state';

// Parameters for each factory field are shared.
const userDataFactory = createGraphNodeFactory({
  // Generate key based on parameter
  key: (id) => id,
  // Get user by id
  get: (id) => () => getUserById(id),
})

// ...
const userData = useGraphNodeValue(userDataFactory(myId));

Factories with Promise-returning graph nodes can be wrapped with createGraphNodeResourceFactory, which automatically converts the generated Promises into Resources.

const userDataResourceFactory = createGraphNodeResourceFactory(userDataFactory);

// ...
const userData = useGraphNodeResource(userDataResourceFactory(myId));

License

MIT © lxsmnsyc

Readme

Keywords

none

Package Sidebar

Install

npm i @lxsmnsyc/preact-graph-state

Weekly Downloads

0

Version

0.3.0

License

MIT

Unpacked Size

479 kB

Total Files

73

Last publish

Collaborators

  • lxsmnsyc