State management

In order for layers to communicate with each other, they require a method for passing state between them. Additionally, the re-rendering cycle for state per layer needs to be managed, and there may also be a need for passing state between sibling components or other external actors.

Although a variety of state libraries can be utilised with the Unless Framework, it already includes the built-in StoreStack.

Global state management

In some cases, you might need to manage state that is shared across multiple components, layers, or even external actors. Global state management can help you handle these scenarios efficiently. Although the Unless Framework is compatible with various state libraries, it includes the built-in StoreStack for convenience.

Observing a store

To observe a store, create an observer using the useObserver function. This function accepts a callback that will be called when the store's state changes:

const observer = useObserver((currentState, prevState) => {
  console.log('State changed from', prevState, 'to', currentState);
});

Then, attach the observer to the store using the store.attach method:

store.attach(observer);

You can also detach an observer from a store using the store.detach method:

store.detach(observer);

Using global observers

If you want to use global observers that apply to all stores, you can use the StoreStack.attach method:

const globalObserver = useObserver((currentState, prevState) => {
  console.log('Global observer:', currentState, prevState);
});

Stores.addGlobalObserver(globalObserver);

Using useState

useState is the preferred method for managing state in StoreStack. It is a convenience method that combines creating, accessing, and updating a store. It is similar to the useState hook in React:

const [getState, setState] = Stores.useState({
  pointer: 'myPointer',
  defaultValue: 0,
});

console.log(getState()); // Output: 0
setState((prevState) => prevState + 1);
console.log(getState()); // Output: 1

Connecting store(s) to the component rendering cycle.

🚧

Rendering Performance

To optimise performance, it's recommended not to use a global observer for the Stores to re-render the component. This is because re-rendering could be triggered by events that are outside the component scope, and outside of its relevant stores.

To connect a store to the rendering cycle, we must attach an observer in the constructor of the layer with the requestUpdate.

For example:

constructor(...) {
	super(...)
	Stores.get(pointer)?.attach(
		useObserver(() => this.requestUpdate())
	)
}

This of course assumes that this.requestUpdate() exists, which is the case for Display and Layout. For Element, this must be manually passed down.

Layer level management

Display and Layout components have built-in state management at the layer level. This can be accessed through this.state, which is an instance of StoreStack that is isolated to the layer and pre-configured with a global observer that triggers re-rendering on change.

For more information on the specific API, see the StoreStack documentation.

To implement similar behavior in an Element, follow the steps below:

Step 1: Pass down the requestUpdate hook

First, pass down the requestUpdate hook to the Element component. This allows the Element component to trigger a re-render when the state changes.

interface MyElementProps extends ElementProps {
  updateHook: (...args: any[]) => void;
}

Step 2: Instantiate an instance of StoreStack

In the Element component, instantiate an instance of StoreStack and attach an observer that calls the updateHook function when the state changes:

export class MyElement extends Element {
  private state: StoreStack;

  constructor(props: MyElementProps) {
    super(DefaultElementSettings);
    this.state = StoreStack.attach([useObserver(() => props.updateHook())]);
  }

  ...
}

Now, when the state changes in the Element component, the updateHook function will be called, and the parent component will re-render accordingly.

By following these steps, you can implement layer-level state management in your Element components using the StoreStack library. This provides a consistent and efficient way to manage state and re-render components when necessary.


What’s Next