npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@state-management/state-machine-react

v1.0.14

Published

React wrapper for simple-state-machine, provides fromState and useDispatcher hooks and unit test fixtures

Downloads

900

Readme

state-machine-react

npm version Build Status MIT License

state-machine-react is a React-specific wrapper for the core library, simple-state-machine. It provides React hooks for integrating the simple-state-machine into React applications. And a utility for simplifying unit testing of components using the state machine. You can find the complete documentation of the core library, here: Simple State Machine Documentation

About This Project

Managing state in React applications can often be complex and cumbersome, especially when dealing with global or shared state. state-machine-react simplifies this by combining the power of a state machine with React's modern hooks API. This library:

  • Provides the fromState hook to observe and react to state changes.
  • Offers the useDispatcher hook to dispatch state-changing commands which encapsulate business logic .
  • Includes setupMockStateMachine to make unit testing with the state machine easier.

This project is part of the state-management suite, which includes:

By decoupling state management logic from UI components, state-machine-react promotes cleaner, more maintainable, and testable React code. Since the state can be modified from within a Command only, this will result in business logic moving out of UI components into command classes.

Implementation Examples

  • Sample React Web App that you can clone. It is a web app example with unit tests, showcasing the use of state-machine-react wrapper.
  • Sample React Native Mobile App that you can clone. It is a React Native mobile app example with unit tests, showcasing the use of state-machine-react wrapper.

Features

State management code, that is lot less scary, easy to read, easy to trace, and very easy to change and unit test.

Traceability:

This single most important feature that we wanted to design correctly is traceability of code. When trying to identify an issue, we should be able to go through the code, and identify the cause, without having to open ten different files. We should be able to use the IDE's "find references" or even the simple Find (Ctrl + F) feature to quickly identify what StateKeys are changed by which Commands.

This is invaluable while identifying issues in code. This also reduces the dependency on debugging tools and time spent in debugging.

Most importantly the state management code looks a lot less scary, it is easy to read, and it is very easy to change and unit test.

Important Technical Features:

  • React Hooks:

    • fromState: Subscribe to and observe changes in the global state.
    • useDispatcher: Dispatch commands to modify global state. Commands contain the business logic. And modify state as part of execution of business logic.
  • Unit Testing Utilities:

    • setupMockStateMachine: Simplify mocking the state machine during component tests.
  • Integration with simple-state-machine:

    • Leverage the powerful state management capabilities of simple-state-machine and its Command class which encapsulates the business logic, keeping the UI code clean.

Compatibility

  • This package has been tested with React versions 17.x through 18.x.
  • It is expected to work with newer React versions as well, but compatibility with versions beyond 18.x has not been explicitly verified.
  • For unit testing, this library supports Jest 26.x and higher.

Installation

To install this package, run:

npm install @state-management/state-machine-react

OR

yarn add @state-management/state-machine-react

Usage

StateKeys.constants.ts

Create a constants file to store all state keys, for easy tracing of state changes in application

import { StateKey } from '@state-management/simple-state-machine';

# NOTE: the generics in the StateKey defines the data type of the value stored against this key.
export const CounterKey = new StateKey<number>('Counter');

useDispatcher

The useDispatcher hook provides a simple way to dispatch commands to the state machine. The commands contain the business logic. This allows you to separate business logic from UI code. The commands update the state as part of their execution.

Note: Only Commands can update the global state.

Create a command to dispatch

import React from 'react';
import { Command } from '@state-management/simple-state-machine';
import { CounterKey } from 'constants/StateKeysConstants';

// NOTE: The generics "<number>" below, defines the data type of the parameter passed to the "execute" method.
class IncrementCounterCommand extends Command<number> {
    execute(incrementBy: number): void {
        const current = this.getLatest(CounterKey) || 0;
        
        // NOTE: only commands can call putState and update the state.
        this.putState(current + incrementBy);
    }
}

Dispatch the command

The application code dispatches commands to execute the business logic and update state.

import React from 'react';
import { IncrementCounterCommand } from 'IncrementCounterCommand';
import useDispatcher from '@state-management/state-machine-react';

export const CounterControls: React.FC = () => {
    const dispatch = useDispatcher();

    const handleIncrement = () => {
        // NOTE:  the constructor argument of IncrememntCounterCommand is type checked using generics in Command class.
        // This constructor argument will be passed to the "execute" method of the command.
        dispatch(new IncrementCounterCommand(1));
    };

    return <button onClick={handleIncrement}>Increment</button>;
};

UpdateStateCommand for Quick, one-off state change, without creating a new Command

import React from 'react';
import { UpdateStateCommand, useDispatcher }  from '@state-management/state-machine-react';
import { CounterKey } from 'constants/StateKeysConstants';

export const CounterControls: React.FC = () => {
    const dispatch = useDispatcher();

    const handleIncrement = () => {
        // NOTE:  the generics used in StateKey provides type safety check for the value.
        dispatch(new UpdateStateCommand({stateKey: CounterKey, value: 42}));
    };

    return <button onClick={handleIncrement}>Increment</button>;
};
Note: For easy tracing and debugging, do not re-use the command class to make state changes from different parts of the application.

In this example the initial value of the "CounterKey" in this example can be set from, say

  • Application load
  • Click of a reset button.

It is recommended that, for both scenarios, use a different command object, which can call the same "service" class containing the logic to set the initial value.


fromState

The fromState hook allows you to subscribe to a specific key in the state machine and update the component when the state changes.

Example

import React from 'react';
import fromState from '@state-management/state-machine-react';
import { CounterKey } from 'constants/StateKeysConstants';

export const CounterDisplay: React.FC = () => {
    // NOTE:  the generics used in StateKey defines the data type of counter as "number"
    const counter = fromState(CounterKey);

    return <h1>Counter: {counter}</h1>;
};

Unit Testing

state-machine-react includes a utility, setupMockStateMachine, to simplify testing React components that use the state machine. It allows you to specify default values and mock implementations for testing purposes.

Example

import React from 'react';
import { render, screen } from '@testing-library/react';
import { setupMockStateMachine } from '@state-management/state-machine-react/testing';
import { CounterDisplay } from './CounterDisplay';

describe('CounterDisplay', () => {
    let mockStateMachine: any;

    beforeEach(() => {
        mockStateMachine = setupMockStateMachine({
            defaultValue: 42,
        });
    });

    it('renders the counter with the default value', () => {
        render(<CounterDisplay />);
        expect(screen.getByText(/Counter: 42/i)).toBeInTheDocument();
    });

    it('subscribes to state changes', () => {
        expect(mockStateMachine.onChange).toHaveBeenCalled();
    });
});

API Documentation

The state-machine-react is a React-specific wrapper for the core library, simple-state-machine. You can find the API documentation of the core library, here: Simple State Machine Documentation

fromState<T>(key: StateKey<T>): T | undefined

The fromState hook subscribes to the state associated with the provided StateKey and returns the current value. The hook automatically updates the value when the state changes.

| Parameter | Type | Description | |-----------|----------------|----------------------------------------------| | key | StateKey<T> | The key for the state to subscribe to. |

| Returns | Type | Description | |---------|-----------------|-----------------------------------------------| | T | T or undeifined | The current value for the key or undefined. |

Example:

import { StateKey } from '@state-management/simple-state-machine';
import fromState from '@state-management/state-machine-react';

const CounterKey = new StateKey<number>('counter');

const counter = fromState(CounterKey);
console.log(counter); // Logs the current value of 'counter' state

useDispatcher<T>(): (command: Command<T>) => void

The useDispatcher hook returns a function to dispatch commands to the state machine. Commands encapsulate logic and allow you to separate the business logic from UI code.

| Returns | Type | Description | |----------------|---------------------------------|------------------------------------| | dispatch | (command: Command<T>) => void | Dispatches the given command. |

Example

import React from 'react';
import useDispatcher from '@state-management/state-machine-react';

const ResetCounterButton: React.FC = () => {
    const dispatch = useDispatcher();

    const handleReset = () => {
        dispatch(new IncrementCounterCommand(1));
    };

    return <button onClick={handleReset}>Reset Counter</button>;
};

setupMockStateMachine

The setupMockStateMachine utility simplifies unit testing by providing a mocked implementation of the state machine. This allows you to simulate state changes and command dispatching in tests.

| Parameter | Type | Default | Description | |--------------------------|--------------------------|-------------|---------------------------------------------| | defaultValue | any | 0 | The default value returned by getLatest. | | dispatchImplementation | jest.Mock | jest.fn() | Custom mock implementation for dispatch. |

| Returns | Type | Description | |----------------------|------------------|--------------------------------------| | mockStateMachine | any | The mocked state machine instance. |

Example

import { setupMockStateMachine } from '@state-management/state-machine-react/testing';

// Set up a mocked state machine
const mockStateMachine = setupMockStateMachine({
    defaultValue: 42,
    dispatchImplementation: jest.fn((command) => console.log('Dispatched:', command)),
});

// Mock getLatest method to return specific values
mockStateMachine.getLatest.mockImplementation((key) => {
    if (key.name === 'counter') return 42;
    return undefined;
});

// Mock onChange method to simulate subscription
mockStateMachine.onChange.mockImplementation((key, callback) => {
    callback(42); 
    return { unsubscribe: jest.fn() }; 
});

// Use mockStateMachine in your tests
expect(mockStateMachine.getLatest).toHaveBeenCalled();
expect(mockStateMachine.dispatch).toHaveBeenCalledWith(expect.any(Object));

Contributing

We welcome contributions! Please open an issue or submit a pull request if you’d like to improve the library.

We welcome contributions! Please open an issue or submit a pull request if you’d like to improve the library.

Report issues or request features

Found a bug or have an idea for a new feature? Let us know by opening an issue.

Discuss features and approaches

Start a discussion in the Discussions tab.

Contribute code via pull requests.

See our contributing guidelines.