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

react-state-history-tree

v1.1.7

Published

Included in this package: * a hook that extends React useState and stores state history, providing multiple-choice, customizable undo/redo functionality to states of any type (not just text inputs) * a text input React component that uses the aforemention

Downloads

31

Readme

React State History Tree

Included in this package:

  • a hook that extends React useState and stores state history, providing multiple-choice, customizable undo/redo functionality to states of any type (not just text inputs)
  • a text input React component that uses the aforementioned hook and provides forked redo functionality for the user in the form of a popup widget near the text caret

Note: "History" as mentioned in this document has no relation to the react router history.

Example of ForkedRedoTextField

Installation

This package can be installed via npm:

npm install react-state-history-tree --save

Or via yarn:

yarn add react-state-history-tree

React must be installed separately, as it is not included in the dependencies. The hook and component can be imported as follows:

import { useStateHistoryTree, ForkedRedoTextField } from "react-state-history-tree";

Background

React states do not store a history of their previous values. This must be implemented by the developer. The redux docs suggest one way to implement undo history: https://redux.js.org/recipes/implementing-undo-history.

Traditionally, the undo/redo functionality provides a single thread of history. If one undos and then rewrites the state, the former redo history is lost. This package therefore provides a solution that retains all redo histories no matter how many undos and rewrites are created.

Additionally, undo/redo functionality is usually associated with text input. However, extending this functionality to non-text-based inputs is a logical and useful abstraction. Graphics editors such as Adobe Photoshop have been implementing this functionality for a while.

Documentation

useStateHistoryTree

The useStateHistoryTree hook can be used as follows:

const [state, setState, utilities] = useStateHistoryTree(initialState)

The first two return values follow the [state, setState] return convention from React's useState hook.

useStateHistoryTree's second return value, named setState above has the following signature:

setState(State | State => State, Boolean=true) => void

Update (version >1.1.1): Support for conditional committing to the history tree is now supported. Inspiration for this functionality is from ayc0's use-history-state.

setState supports functional state updates. The second argument (defaulted to true) commits the state update to the history tree if true and updates state without committing to the history tree if false. Therefore, if one wishes to create a state that can be rolled back to in the future, leave this value as true. If one wants a normal state update (also saving memory), set this value to false.

Utilities is an object that has the following fields:

| Field | Type | Description | | ----------- | ----------- | ----------- | | undo | (toClosestFork: boolean) => void | If toClosestFork=false, the state is set to the previous state. If toClosestFork=true, the state is set to the closest state in the past that had more than one redo branch. toClosestFork defaults to false. | | redo | (pathIndex: number, toClosestFork: boolean) => void | If pathIndex is set to a valid index of the current redo branches, the state is set to the redo state with that index. If it is not, the default redo path is used (either decided by the most recent rewrite or the most recent redo, whichever one came last). If toClosestFork=false, the state is set to the previous state. If toClosestFork=true, the state is set to the closest state in the past that had more than one redo branch. pathIndex defaults to null and toClosestFork defaults to false. | | getCurrentBranches | () => [branch: {index: number, value}] | Returns the redo branches of the current state. | | getCurrentSubtree | () => [node: {index: number, value, children: [node]}] | Returns the same redo branches as getCurrentBranches, but includes nested children for deeper navigation. | | defaultKeyDownHandler | (keydown event) => void | This callback implements the default behavior for undo/redo: Ctrl + z for undo and Ctrl + Shift + z for redo (command instead of Ctrl is used for Mac users). IMPORTANT NOTE: When defaultKeyDownHandler is applied to certain tags (including div), the tabindex attribute must be set to "0" for the handler to work properly.| | atRoot | boolean | true if current state is the initial state, false otherwise.| | atLeaf | boolean | true if current state has no redo branches, false otherwise.|

ForkedRedoTextField

ForkedRedoTextField is a React component that applies the useStateHistoryTree hook to an input or textarea. The component uses the defaultKeyHandler in conjunction with a listener that opens a widget near the text caret cursor for selecting the desired redo branch when the user enters Ctrl + y (command instead of Ctrl is used for Mac users). The component allows for several stylistic customizations.

Props for ForkedRedoTextField:

| Prop | Type | Default | Description | | ----------- | ----------- | ----------- | ----------- | | multiline | boolean | false | Uses <textarea/> if true, <input/> otherwise. | | rows | number | 3 | The number of rows if multiline. | | inputStyles | jsx style object | N/A | Override styles applied to the input element. | | unSelectedCellStyles | jsx style object | N/A | Override styles applied to the unselected widget cells. | | selectedCellStyles | jsx style object | N/A | Override styles applied to the selected widget cell. | | cellAreaStyles | jsx style object | N/A | Override styles applied to the cell area in the widget. | | doButtonStyles | jsx style object | N/A | Override styles applied to the undo/redo to closest fork buttons in the widget. | | widgetContainerStyles | jsx style object | N/A | Override styles applied to the widget container. |

Navigating the ForkedRedoTextField widget

The widget/popup can be used to select the desired redo branch to set as the state:

  • Ctrl + y (command + y for Mac users) opens the widget.
  • Clicking the corresponding cell will expand the cell if needed to see the entire value.
  • Clicking on a selected cell submits the selection.
  • The Left and Right keyboard arrows can be used to cycle through the cells.
  • Enter key can be used to submit a selection.
  • The left bracket and right bracket buttons in the widget can be used to trigger, respectively, undo or redo to the closest fork (described above).

Compatibility

React

This package uses hooks, so React 16.8 or newer is required.

Implementation

Certain text editors may implement undo/redo functionality by building diff histories, which saves immensely on memory for large files. Other implementations store a reversible action for every forward action. Finally, in the case of editors that support irreversible or computationally-expensive-to-reverse actions, especially those of graphics editors, there may be no other choice but to save entire states. Still other implementations use a hybrid approach of the aforementioned implementations. This package is oblivious to the types of states being saved; therefore, it opts for the universal (though storage-intensive for large states) implementation of saving entire states to the tree.

Under the hood, the useStateHistoryTree hook stores each iteration of the state as a node in a tree data structure. The initial state is stored at the root. When the state is updated, a node representing the new state is added as a child of the previous state. Each node has access to its parent (its undo state) and its children (its redo states). Each node stores its default redo path, which is either decided by the most recent rewrite or the most recent redo (whichever one comes later has priority). Therefore, at any given time, there always exists a default path from the root (initial state) to a leaf. This default path can be navigated on by traditional the Ctrl + z for undo and Ctrl + Shift + z for redo, as is familiar to most users (the defaultKeyDownHandler callback provides this functionality). The default path is only changed when the user rewrites or redos to a specific branch, as discussed before.

Redos are only interesting when there are multiple options. These are represented as nodes in the tree with more than one child. Throughout the docs, this situation is called a "fork".

Example

Below is an example of using the useStateHistoryTree hook on a non-text-input state and applying the defaultKeyDownHandler to the window to control the background color of a div. Note that the state updates that do not change the color are not committed to the history tree. Use of a ForkedRedoTextField component is also demonstrated.

export default function Test() {
    const [color, setColor, 
              {
                  undo, 
                  redo, 
                  getCurrentBranches, 
                  getCurrentSubtree, 
                  defaultKeyDownHandler, 
                  atRoot, 
                  atLeaf
              }
        ] = useStateHistoryTree("blue")

    const ColorButton = ({buttonColor}) => 
        <button 
            // update state, but only commit to history tree 
            // if color differs from current
            onClick={() => setColor(buttonColor, color != buttonColor)}
        >
            {buttonColor}
        </button>

    useEffect(() => {
        window.addEventListener("keydown", defaultKeyDownHandler)
        return () => {
            window.removeEventListener("keydown", defaultKeyDownHandler)
        }
    })

    return (
        <>
            <div style={{backgroundColor: color, color: "white"}}>{color}</div>
            <ColorButton buttonColor="yellow"></ColorButton>
            <ColorButton buttonColor="blue"></ColorButton>
            <ColorButton buttonColor="red"></ColorButton>
            <ColorButton buttonColor="green"></ColorButton>
            <ForkedRedoTextField multiline></ForkedRedoTextField>
        </>
    );
};

Issues and Acknowledgments

This package is in early development. Thank you for taking the time to read about, use, and make suggestions for this package. All issues and inquiries can be directed to GitHub.

Thank you to ayc0 for the inspiration of providing a parameter to determine when to commit to the history tree. Thank you to Alexander Zsikla and Andrew Zhang for their suggestions regarding the documentation.