{"id":13519004,"url":"https://github.com/Harvtronix/react-substate","last_synced_at":"2025-03-31T12:30:59.780Z","repository":{"id":38454844,"uuid":"255159858","full_name":"Harvtronix/react-substate","owner":"Harvtronix","description":"Blazing-fast, centralized state management with auto-guaranteed, immutable state changes","archived":false,"fork":false,"pushed_at":"2025-03-01T01:48:15.000Z","size":4826,"stargazers_count":4,"open_issues_count":9,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-12T11:17:53.056Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Harvtronix.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-12T19:58:59.000Z","updated_at":"2025-02-12T05:10:20.000Z","dependencies_parsed_at":"2024-04-04T04:25:41.448Z","dependency_job_id":"f71b9763-3afa-4f27-91f1-7379dae090d1","html_url":"https://github.com/Harvtronix/react-substate","commit_stats":{"total_commits":139,"total_committers":2,"mean_commits":69.5,"dds":0.2158273381294964,"last_synced_commit":"cc8cd0347ec04c425ae054a2e73aef20dd807712"},"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Harvtronix%2Freact-substate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Harvtronix%2Freact-substate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Harvtronix%2Freact-substate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Harvtronix%2Freact-substate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Harvtronix","download_url":"https://codeload.github.com/Harvtronix/react-substate/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246469027,"owners_count":20782648,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T05:01:52.029Z","updated_at":"2025-03-31T12:30:59.773Z","avatar_url":"https://github.com/Harvtronix.png","language":"TypeScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# React Substate\n\n\u003e Blazing-fast, centralized state management with auto-guaranteed, immutable state changes\n\n[![Package Name](https://img.shields.io/badge/pkg%20name-react--substate-blueviolet)](https://www.npmjs.com/package/react-substate)\n[![NPM Version](https://img.shields.io/npm/v/react-substate.svg)](https://www.npmjs.com/package/react-substate)\n![Minified and Zipped Size is 5.58 kB](https://img.shields.io/badge/minified%2Bzipped-5.58%20kB-brightgreen)\n[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-orange.svg)](https://standardjs.com)\n[![License](https://img.shields.io/npm/l/react-substate?color=orange)](https://github.com/Harvtronix/react-substate/blob/main/LICENSE)\n[![CI](https://github.com/Harvtronix/react-substate/workflows/CI/badge.svg)](https://github.com/Harvtronix/react-substate/actions?query=workflow%3ACI)\n![Publish to NPM](https://github.com/Harvtronix/react-substate/workflows/Publish%20to%20NPM/badge.svg)\n\n## Install\n\n```bash\nnpm install react-substate [react react-dom]\n```\n\n## Basic Example\n\n```jsx\nimport { createSubstate, createAction, useSubstate } from 'react-substate'\n\n// Set up some sub-states\nconst substates = {\n  test: createSubstate({ someField: 'the state' }),\n  anotherTest: createSubstate(() =\u003e ({ foo: 'bar' })) // Use a generator function\n}\n\n// Set up some dispatchable Actions to modify state\nconst actions = {\n  updateSomeField: createAction((draft, payload) =\u003e {\n    draft.someField = payload // Sets `someField` in `draft` to the provided `payload`\n  })\n}\n\n// Use it!\nexport const Component = () =\u003e {\n  const test = useSubstate(substates.test)\n\n  const handleClick = useCallback(() =\u003e {\n    test.dispatch(actions.updateSomeField, 'the new state') // works\n  }, [])\n\n  return \u003cbutton onClick={handleClick}\u003e{test.value.someField}\u003c/button\u003e\n}\n```\n\n## React Substate supports Redux DevTools\n\nIf you have the Redux DevTools extension installed in your browser, you'll be able to see changes driven by Substate creations and Action dispatches as they happen over time. The support is somewhat limited for now, but will only get better with time!\n\n## Migrating from 5.x to 6.x\n\nThe 6.0 release includes breaking changes to what `useSubstate` returns, as well as the removal of the Immer \"patch\" support that was previously exposed via `usePatchEffect`. The `globalDispatch` function has also been renamed to just `dispatch`.\n\n### `useSubstate`\n\nWhere you previously had something like:\n\n```jsx\nconst [test, dispatch] = useSubstate(substates.test)\n```\n\nOr as was often the case in larger applications:\n\n```jsx\nconst [test, dispatchTest] = useSubstate(substates.test)\n```\n\nYou will instead use the clearer and less error-prone syntax of:\n\n```jsx\nconst test = useSubstate(substates.test)\n```\n\nTo get the current value of a substate, use:\n\n```jsx\ntest.value\n```\n\nAnd to get the Substate-specific dispatch function, use:\n\n```jsx\ntest.dispatch(...)\n```\n\nIf you still really want to destructure these into `{value: test, dispatch: testDispatch}` you can, however this is not the recommended approach.\n\n### `useGlobalDispatch`\n\nWhere you previously had something like:\n\n```jsx\nconst globalDispatch = useGlobalDispatch()\n```\n\nYou will instead use:\n\n```jsx\nconst dispatch = useDispatch()\n```\n\n### `usePatchEffect`\n\nThis hook has been removed and there is no planned replacement for it. If you still need its functionality, use v5.x instead.\n\n### TypeScript enhancements\n\nThe typing of React Substate is now better ar preventing users from doing the \"wrong thing\" by carrying forward types from Substate definitions all the way through to Actions verbatim and no longer widening types when additional properties are provided to either drafts or payloads.\n\n# Intro\n\nReact Substate boils down to three main parts:\n\n## Substates\n\nThis is how you store your application state. You can create new substates wherever you want, but it's often useful to define related groups of them together in the same file.\n\nWhen you create a substate, what you get back is a \"key\" which is used later on to refer to the Substate in other functions of the library.\n\nSubstates are inexpensive, so you have the freedom to define them based on how you'd like to trigger re-renders within your application.\n\nFew, large Substates will lead to heavier and more frequent re-renders, but can be useful in applications where even the most nested of components require a lot of data, or when the application is sufficiently small.\n\nMany, small Substates generally leads to better-designed applications with fewer re-renders. The disadvantage of this approach is some additional legwork to adequately divide your application state into Substates.\n\n## Actions\n\nLike other action/dispatch-driven frameworks, React Substate requires that state be updated through discrete Actions previously registered with the framework. These Actions can be created/registered at any time, but it is advisable to define them up front and all together in their own file(s).\n\nWhat sets React Substate apart from other state management libraries is immutability. When working with your state inside of an Action, the object is automatically proxied by [Immer](https://immerjs.github.io/immer/) to ensure that no matter how you manipulate the state inside of the Action, the result is an **immutable** state change. By convention, _Immer_ refers to the proxied state object as a `draft`, and it's always the first parameter available inside of your Action functions.\n\nAn Action can be used to update any Substate. You have the flexibility to choose whether to write general-purpose Actions that can apply to multiple different Subsates with similar structures or very specific Actions that only make sense when called against a single Substate. It is often easier to debug an application when the Actions are specific and discrete, but this is not required by the framework. For example, a single, giant Action called `doUpdate` with a bunch of conditionals in it is possible, but most likely not a great idea.\n\nActions you create can later be passed to a `dispatch` function to cause your Substates to change, and ultimately your components to re-render. `dispatch` takes an `action` and a `payload` as arguments. The payload can be anything you might need to calculate the new state from inside your Action.\n\n## Hooks\n\nReact Substate's Hooks are what give you access to your state and changes to that state. A component can use as many `useSubstate` hooks as needed to obtain the data it needs to render.\n\nIn addition to giving back the current `value` of a Substate, `useSubstate` returns a `dispatch` function that can be called (with an `action` and `payload`) to update the value of that particular Substate.\n\nDepending on your preference, you can also opt to use the general-purpose `useDispatch` hook instead of dealing with Substate-specific ones. `useDispatch` returns a function which takes three arguments instead of two: A Substate key, an Action key, and a payload. More on this in the examples below.\n\n# Examples\n\n## Basic TypeScript example\n\n```tsx\nimport { useCallback } from 'react'\nimport {\n  createSubstate,\n  createAction,\n  useSubstate\n} from 'react-substate'\n\ninterface Test {\n  someField: string\n}\n\nconst substates = {\n  // By default, the type of the Substate will be inferred from the provided argument\n  simple: createSubstate({foo: 'bar'})\n  // A type hint can be provided to be more specific.\n  test: createSubstate\u003cTest\u003e({someField: 'the state'})\n}\n\nconst actions = {\n  updateSomeField: createAction(\n    // The Subtate's type can then also be used in the Action that modifies the Substate\n    (draft: Test, payload: Test['someField']) =\u003e {\n      draft.someField = payload // Will become \"the new state\"\n    }\n  )\n}\n\nexport const Component = () =\u003e {\n  const test = useSubstate(substates.test)\n\n  const handleClick = useCallback(() =\u003e {\n    test.dispatch(actions.updateSomeField, 'the new state') // works\n    // test.dispatch(actions.updateSomeField, 123) \u003c-- error: must pass a string\n  }, [])\n\n  return (\n    \u003cbutton onClick={handleClick}\u003e{test.value.someField}\u003c/button\u003e\n  )\n}\n```\n\n## Multiple Substates, One Dispatcher\n\n```tsx\nimport { useCallback } from 'react'\nimport {\n  createSubstate,\n  createAction,\n  useSubstate,\n  useDispatch\n} from 'react-substate'\n\nconst substates = {\n  simple: createSubstate({foo: 'bar'})\n  test: createSubstate({someField: 'the state'})\n}\n\nconst actions = {\n  updateFoo: createAction((draft, payload) =\u003e {\n    draft.foo = payload\n  }),\n  updateSomeField: createAction(\n    (draft, payload) =\u003e {\n      draft.someField = payload\n    }\n  )\n}\n\nexport const Component = () =\u003e {\n  const simple = useSubstate(substates.simple)\n  const test = useSubstate(substates.test)\n  const dispatch = useDispatch()\n\n  const handleClick = useCallback(() =\u003e {\n    dispatch(substates.simple, actions.updateFoo, 'new foo!')\n    dispatch(substates.test, actions.updateSomeField, 'the new state')\n  }, [])\n\n  return (\n    \u003cbutton onClick={handleClick}\u003e\n      {simple.value.foo} {test.value.someField}\n    \u003c/button\u003e\n  )\n}\n```\n\n## Replacing the entire Substate value\n\n```tsx\nimport { useCallback } from 'react'\nimport { createSubstate, createAction, useSubstate } from 'react-substate'\n\nconst substates = {\n  test: createSubstate({ someField: 'the state' })\n}\n\nconst actions = {\n  resetTest: createAction((_draft, _payload) =\u003e {\n    // Just like Immer's `produce`, returning a value replaces the draft entirely\n    return {\n      someField: 'the brand new state'\n    }\n  })\n}\n\nexport const Component = () =\u003e {\n  const test = useSubstate(substates.test)\n\n  const handleClick = useCallback(() =\u003e {\n    test.dispatch(actions.resetTest, null)\n  }, [])\n\n  return \u003cbutton onClick={handleClick}\u003e{test.value.someField}\u003c/button\u003e\n}\n```\n\n## Unit testing\n\n```tsx\nimport { render, screen } from '@testing-library/react'\nimport { createSubstate } from 'react-substate'\n\nimport { substates } from '../substates.js'\nimport { Component } from '../component.js'\n\ndescribe('Cool unit tests', () =\u003e {\n  it('works when given a specific value', () =\u003e {\n    substates.test = createSubstate({ something: 'very specific' })\n\n    render(\u003cComponent /\u003e)\n\n    expect(screen.getByRole('button')).toHaveTextContent('very specific')\n  })\n})\n```\n\n# API Reference\n\n## Functions\n\n### `createSubstate`\n\nCreates and registers a new Substate with the given initial data. Returns a \"key\" for the Substate that can be passed to other functions like `useSubstate` or `dispatch`.\n\n### `createAction`\n\nRegisters a new dispatchable Action that modifies a Substate. Returns a \"key\" for the Action that can be passed to a `dispatch` function.\n\n### `setDebugEnabled`\n\nTurns on/off logging of debug statements to the JavaScript console.\n\n### `setDevToolsEnabled`\n\nTurns on/off logging of Substate changes to the Redux DevTools browser extension.\n\n## Hooks\n\n### `useSubstate`\n\nHook that allows a component to listen for changes to a Substate and receive a reference to a dispatch function that can be called to update that Substate. The return value is an object of the form `{ value: \u003cobj\u003e, dispatch: \u003cfn\u003e }`.\n\n### `useDispatch`\n\nHook that returns a reference to a dispatch function that can be called to update any provided Substate without also listening for changes to any Substates.\n\n## Configuration\n\n### `ImmerConfig`\n\nReact Substate uses Immer under the covers to ensure state changes happen in an immutable way. Immer is left at its default behavior except for one exception: Auto-freezing is turned off by default to speed up performance.\n\nIf you want to turn this back on or configure any other aspects of Immer in your application, you can use the exported functions like so:\n\n```tsx\nimport { ImmerConfig } from 'react-substate'\n\nImmerConfig.setAutoFreeze(true)\nImmerConfig.useMapSet(true)\n\n// etc.\n```\n\n# Peer Dependencies\n\nThis module has peer dependencies on:\n\n- `react` version 16.14 (with hooks support) or higher.\n- `react-dom` version 16 or higher.\n\n# License\n\nMIT © [Harvtronix](https://github.com/Harvtronix)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHarvtronix%2Freact-substate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHarvtronix%2Freact-substate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHarvtronix%2Freact-substate/lists"}