{"id":43011060,"url":"https://github.com/szhsin/reactish-state","last_synced_at":"2026-01-31T05:10:44.794Z","repository":{"id":103423234,"uuid":"550174033","full_name":"szhsin/reactish-state","owner":"szhsin","description":"Simple, decentralized (atomic) state management for React","archived":false,"fork":false,"pushed_at":"2026-01-18T09:59:51.000Z","size":2213,"stargazers_count":55,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-18T17:38:57.112Z","etag":null,"topics":["react","state","state-management"],"latest_commit_sha":null,"homepage":"","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/szhsin.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-10-12T10:17:45.000Z","updated_at":"2026-01-18T09:59:54.000Z","dependencies_parsed_at":"2023-05-27T21:45:47.157Z","dependency_job_id":"889f727e-c7fe-4dfe-bf17-24f3a0aa6b40","html_url":"https://github.com/szhsin/reactish-state","commit_stats":{"total_commits":122,"total_committers":2,"mean_commits":61.0,"dds":"0.016393442622950838","last_synced_commit":"95aad38e2367166b5116f4354cb7342dc83092c1"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/szhsin/reactish-state","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/szhsin%2Freactish-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/szhsin%2Freactish-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/szhsin%2Freactish-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/szhsin%2Freactish-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/szhsin","download_url":"https://codeload.github.com/szhsin/reactish-state/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/szhsin%2Freactish-state/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28929866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T04:05:25.756Z","status":"ssl_error","status_checked_at":"2026-01-31T04:02:35.005Z","response_time":128,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["react","state","state-management"],"created_at":"2026-01-31T05:10:44.268Z","updated_at":"2026-01-31T05:10:44.787Z","avatar_url":"https://github.com/szhsin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Reactish-State\n\n\u003e Simple, decentralized (atomic) state management for React.\n\n[![NPM](https://img.shields.io/npm/v/reactish-state.svg)](https://www.npmjs.com/package/reactish-state) [![bundlejs](https://deno.bundlejs.com/?q=reactish-state\u0026treeshake=%5B*%5D\u0026config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D\u0026badge=simple)](https://bundlejs.com/?q=reactish-state\u0026treeshake=%5B*%5D\u0026config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D) [![NPM](https://img.shields.io/bundlephobia/minzip/reactish-state)](https://bundlephobia.com/package/reactish-state)\n\n💡 [Quick examples](#examples) \u0026nbsp;\u0026nbsp; 🔧 [TypeScript usage](#typescript-usage)\n\n## ✨ Highlights ✨\n\n- Decentralized (atomic) state management\n- Simple, unopinionated API\n- No Context wrapper or prop drilling required\n- React components re-render only on changes\n- Compatible with React 18/19 concurrent rendering\n- Built-in memoized selectors\n- Extensible with middleware and plugins\n- State persistable to browser storage\n- Redux DevTools support via middleware\n- Fully compatible with React Compiler\n- Ultra-lightweight: [\u003c1KB](https://bundlejs.com/?q=reactish-state\u0026treeshake=%5B*%5D\u0026config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D)\n\n## Install\n\n```bash\nnpm install reactish-state\n```\n\n## Table of Contents\n\n- [Quick start](#quick-start)\n  - [State](#we-begin-by-creating-some-state)\n  - [State with actions](#a-state-can-also-have-custom-actions-bound-to-it)\n  - [Selectors](#selector-can-create-derived-state)\n  - [Usage in React](#use-the-state-and-selectors-in-your-react-components)\n- [Why another library?](#why-another-state-management-library)\n- [Why decentralized?](#why-decentralized-state-management)\n- [Why not Zustand?](#why-choose-this-over-zustand)\n- [Recipes](#recipes)\n  - [Immutable updates](#state-should-be-updated-immutably)\n  - [Memoized selectors](#selectors-are-memoized)\n  - [Async updates](#async-state-updates)\n  - [Accessing other state in actions](#accessing-other-state-or-selectors-inside-actions)\n  - [Usage outside React](#interacting-with-state-or-selectors-outside-react)\n  - [Destructuring actions](#destructuring-actions-for-easier-access)\n  - [useSelector](#selector-that-depends-on-props-or-local-state)\n  - [Redux-like reducers](#still-perfer-redux-like-reducers)\n- [Middleware](#middleware)\n  - [Persist middleware](#persist-middleware)\n  - [Immer middleware](#immer-middleware)\n  - [Redux devtools middleware](#redux-devtools-middleware)\n  - [Multiple middleware](#using-multiple-middleware)\n  - [Different middleware](#using-different-middleware-in-different-states)\n- [Plugins](#plugins)\n  - [Redux devtools plugin](#redux-devtools-plugin)\n- [TypeScript usage](#typescript-usage)\n- [Examples](#examples)\n- [React 16/17 setup](#react-1617-setup)\n\n# Quick start\n\n### We begin by creating some state\n\n```js\nimport { state } from \"reactish-state\";\n\n// `state` can hold anything: primitives, arrays, objects, etc.\nconst countState = state(0);\nconst todos = state([\n  { task: \"Shop groceries\", completed: false },\n  { task: \"Clean the house\", completed: true }\n]);\n\n// Update the state\ncountState.set(10);\n// Read from the state\nconsole.log(countState.get()); // Print 10\n```\n\n### A state can also have custom actions bound to it\n\n```js\nconst countState = state(0, (set, get) =\u003e ({\n  // Set a new state value\n  reset: () =\u003e set(0),\n  // Or use the functional update of `set`\n  increase: () =\u003e set((count) =\u003e count + 1),\n  // State can still be read using `get`\n  decrease: () =\u003e set(get() - 1)\n}));\n\n// Use the custom actions\ncountState.increase();\n```\n\n### `selector` can create derived state\n\n```js\nimport { selector } from \"reactish-state\";\n\n// Derive from another state\nconst doubleSelector = selector(countState, (count) =\u003e count * 2);\n\n// Can also derive from both states and selectors\nconst tripleSelector = selector(\n  countState,\n  doubleSelector,\n  (count, double) =\u003e count + double\n);\n```\n\nA selector will re-compute only when one of the states it depends on has changed.\n\n### Use the state and selectors in your React components\n\nYou can read state and selectors for rendering with the `useSnapshot` hook, and write to state with `set` or actions. _Rule of thumb_: always read from `useSnapshot` in the render function; otherwise, use the `get` method of state or selector (in event handlers or even outside of React components).\n\n```jsx\nimport { useSnapshot } from \"reactish-state\";\n\nconst Example = () =\u003e {\n  const count = useSnapshot(countState);\n  const triple = useSnapshot(tripleSelector);\n\n  return (\n    \u003ch1\u003e\n      {/* The return values of `useSnapshot` are used for rendering */}\n      {count} {triple}\n      {/* Update the state using the custom actions bound to it */}\n      \u003cbutton onClick={() =\u003e countState.increase()}\u003eIncrease\u003c/button\u003e\n      {/* Or update the state using the `set` method directly */}\n      \u003cbutton onClick={() =\u003e countState.set((i) =\u003e i - 1)}\u003eDecrease\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e countState.set(0)}\u003eReset\u003c/button\u003e\n    \u003c/h1\u003e\n  );\n};\n```\n\nThe component will re-render when states or selectors change. No provider or context is needed!\n\n**[Try a sandbox demo!](https://codesandbox.io/p/sandbox/reactish-counter-z42qt7)**\n\n## Why another state management library?\n\nState management solutions in the React ecosystem have popularized two state models:\n\n- **Centralized**: a single store that combines the entire app's state, with slices of the store connected to React components via selectors. Examples: React-Redux, Zustand.\n\n- **Decentralized**: composed of many small (atomic) states that build state dependency trees using a bottom-up approach. React components only connect to the states they need. Examples: Recoil, Jotai.\n\nThis library adopts the decentralized state model, offering a _Recoil-like_ API with a much smaller implementation (similar to Zustand). This makes it one of the smallest state management solutions, with a gzipped bundle size of less than 1KB.\n\n|  | State model | Bundle size |\n| --- | --- | --- |\n| Reactish-State | decentralized | [![NPM](https://img.shields.io/bundlephobia/minzip/reactish-state)](https://bundlephobia.com/package/reactish-state) |\n| Recoil | decentralized | [![NPM](https://img.shields.io/bundlephobia/minzip/recoil)](https://bundlephobia.com/package/recoil) |\n| Jotai | decentralized | [![NPM](https://img.shields.io/bundlephobia/minzip/jotai)](https://bundlephobia.com/package/jotai) |\n| React-Redux | centralized | [![NPM](https://img.shields.io/bundlephobia/minzip/react-redux)](https://bundlephobia.com/package/react-redux) |\n| Zustand | centralized | [![NPM](https://img.shields.io/bundlephobia/minzip/zustand)](https://bundlephobia.com/package/zustand) |\n\n## Why decentralized state management?\n\nCentralized state management typically combines the entire app's state into a single store. To optimize rendering, selectors are used to subscribe React components to slices of the store. Taking the classic [Redux todo example](https://redux.js.org/introduction/examples#todos), the store has the following shape:\n\n```js\n{\n  visibilityFilter: \"ALL\", // ALL, ACTIVE, COMPLETED\n  todos: [{ task: \"Shop groceries\", completed: false } /* ...and more items */]\n}\n```\n\nWe have a `\u003cFilter/\u003e` component that connects to the store with a selector `(state) =\u003e state.visibilityFilter`.\n\nWhen any action updates the `todos` slice, the selector in the `\u003cFilter/\u003e` component needs to re-run to determine if a re-render is required. This is not optimal, as the `\u003cFilter/\u003e` component should not be affected when todos are added, removed, or updated.\n\nIn contrast, decentralized state management may approach the same problem with two separate states:\n\n```js\nconst visibilityFilter = state(\"ALL\"); // ALL, ACTIVE, COMPLETED\nconst todos = state([\n  { task: \"Shop groceries\", completed: false }\n  /* ...and more items */\n]);\n```\n\nAn update to `todos`, which is localized and isolated from other states, does not affect components connected to `visibilityFilter` and vice versa.\n\nWhile the difference might seem insignificant, imagine that every small state update could cause every selector in every component across the entire app to re-run. This suggests that the decentralized state model scales better for large apps. Additionally, benefits like code-splitting are easier to implement with this state model.\n\n## Why choose this over Zustand?\n\n- State updates are localized and isolated from other irrelevant states.\n- No potential naming conflicts among states/actions within a large store.\n- No need to use a React Hook to extract actions from the store.\n- Actions are external to React, eliminating the need to add them to the `useCallback/useEffect` dep array.\n\n# Recipes\n\n## State should be updated immutably\n\n```js\nimport { state } from \"reactish-state\";\n\nconst todosState = state([{ task: \"Clean the house\", completed: true }]);\ntodosState.set((todos) =\u003e [\n  ...todos,\n  { task: \"Shop groceries\", completed: false }\n]);\n```\n\nYou can use the `immer` package to reduce boilerplate code:\n\n```js\nimport produce from \"immer\";\n\ntodosState.set(\n  produce((todos) =\u003e {\n    todos.push({ task: \"Shop groceries\", completed: false });\n  })\n);\n```\n\nOr, simply use the [immer middleware](#immer-middleware).\n\n## Selectors are memoized\n\nSelector has an API similar to the [reselect](https://github.com/reduxjs/reselect#readme) package. You pass in one or more 'input' states or selectors, along with an 'output' selector function that receives the extracted values and returns a derived value. The return value is memoized, ensuring that React components won’t re-render even if a non-primitive value is returned.\n\n```js\nimport { selector } from \"reactish-state\";\n\n// Return a number\nconst totalNumSelector = selector(todosState, (todos) =\u003e todos.length);\n\n// Return a new array\nconst completedTodosSelector = selector(todosState, (todos) =\u003e\n  todos.filter((todo) =\u003e todo.completed)\n);\n\n// Return an object\nconst todoStats = selector(\n  totalNumSelector,\n  completedTodosSelector,\n  (totalNum, completedTodos) =\u003e ({\n    completedNum: completedTodos.length,\n    percentCompleted: (completedTodos.length / totalNum) * 100\n  })\n);\n```\n\nThe only difference between state and selector is that selectors are read-only and don’t have a `set` method.\n\n## Async state updates\n\nJust call `set` when your data is ready:\n\n```js\nconst todosState = state([]);\n\nasync function fetchTodos(url) {\n  const response = await fetch(url);\n  todosState.set(await response.json());\n}\n```\n\nYou can also create async actions bound to a state:\n\n```js\nconst todosState = state([], (set) =\u003e ({\n  fetchData: async () =\u003e {\n    const response = await fetch(/* some url */);\n    set(await response.json());\n  }\n}));\n\n// Use the async action\nawait todosState.fetchData();\n```\n\n## Accessing other state or selectors inside actions\n\nYou might not need it, but nothing prevents you from reading or writing to other state inside an action.\n\n```js\nconst inputState = state(\"New item\");\nconst todosState = state(\n  [{ task: \"Shop groceries\", completed: false }],\n  (set) =\u003e ({\n    add: () =\u003e {\n      set((todos) =\u003e [...todos, { task: inputState.get(), completed: false }]);\n      inputState.set(\"\"); // Reset input after adding a todo\n    }\n  })\n);\n```\n\n## Interacting with state or selectors outside React\n\n```js\nconst countState = state(0);\nconst tripleSelector = selector(countState, (count) =\u003e count * 3);\n\n// Get a non-reactish fresh value\nconst count = countState.get();\nconst triple = tripleSelector.get();\n\n// Listen for updates\nconst unsub1 = countState.subscribe(() =\u003e console.log(countState.get()));\nconst unsub2 = tripleSelector.subscribe(() =\u003e\n  console.log(tripleSelector.get())\n);\n\n// Updating `countState` will trigger both listeners\ncountState.set(10);\n\n// Unsubscribe from listeners\nunsub1();\nunsub2();\n```\n\n## Destructuring actions for easier access\n\nThe `set` or actions of a state don't rely on `this` to work, so you can destructure them for easier reference.\n\n_TIP_: Destructure the actions outside of React components to avoid adding them to the `useCallback/useEffect` dependency array.\n\n```jsx\nimport { state, useSnapshot } from \"reactish-state\";\n\nconst countState = state(0, (set) =\u003e ({\n  increase: () =\u003e set((count) =\u003e count + 1),\n  reset: () =\u003e set(0)\n}));\nconst { increase, reset } = countState;\n\nconst Example = () =\u003e {\n  const count = useSnapshot(countState);\n  return (\n    \u003ch1\u003e\n      {count}\n      \u003cbutton onClick={() =\u003e increase()}\u003eIncrease\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e reset()}\u003eReset\u003c/button\u003e\n    \u003c/h1\u003e\n  );\n};\n```\n\n## Selector that depends on props or local state\n\nThe `selector` function allows us to create reusable derived states outside of React components. In contrast, component-scoped derived states that depend on props or local state can be created using the `useSelector` hook.\n\n```jsx\nimport { state, useSelector } from \"reactish-state\";\n\nconst todosState = state([{ task: \"Shop groceries\", completed: false }]);\n\nconst FilteredTodoList = ({ filter = \"ALL\" }) =\u003e {\n  const filteredTodos = useSelector(\n    () =\u003e [\n      todosState,\n      (todos) =\u003e {\n        switch (filter) {\n          case \"ALL\":\n            return todos;\n          case \"COMPLETED\":\n            return todos.filter((todo) =\u003e todo.completed);\n          case \"ACTIVE\":\n            return todos.filter((todo) =\u003e !todo.completed);\n        }\n      }\n    ],\n    [filter]\n  );\n  // Render the filtered todos...\n};\n```\n\nThe second parameter of `useSelector` is a dependency array (similar to React's `useMemo` hook), where you can specify which props or local state the selector depends on. In the example above, the `FilteredTodoList` component will re-render only if the global `todosState` or the local `filter` prop is updated.\n\n### Linting the dependency array of useSelector\n\nYou can take advantage of the [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) package to lint the dependency array of `useSelector`. Simply add the following configuration to your ESLint config file:\n\n```json\n{\n  \"rules\": {\n    \"react-hooks/exhaustive-deps\": [\n      \"error\",\n      {\n        \"additionalHooks\": \"useSelector\"\n      }\n    ]\n  }\n}\n```\n\n## Still perfer Redux-like reducers?\n\n```js\nconst reducer = (state, { type, by = 1 }) =\u003e {\n  switch (type) {\n    case \"INCREASE\":\n      return state + by;\n    case \"DECREASE\":\n      return state - by;\n  }\n};\n\nconst countState = state(0, (set) =\u003e ({\n  dispatch: (action) =\u003e set((state) =\u003e reducer(state, action), action)\n}));\n\nconst { dispatch } = countState;\ndispatch({ type: \"INCREASE\", by: 10 });\ndispatch({ type: \"DECREASE\", by: 7 });\nconsole.log(countState.get()); // Print 3\n```\n\n# Middleware\n\nYou can enhance state functionality using middleware. Instead of the `state` export, use the `stateBuilder` export from the library. A middleware is a function that receives a state object and returns a new `set` function, which can add custom behavior while still calling the current `set` in the middleware chain.\n\n```js\nimport { stateBuilder } from \"reactish-state\";\n\nconst state = stateBuilder(({ set, get }) =\u003e (...args) =\u003e {\n  set(...args);\n  // Log the state every time after calling `set`\n  console.log(\"New state\", get());\n});\n\n// Now the `state` function has middleware wired up\nconst countState = state(0, (set) =\u003e ({\n  increase: () =\u003e set((count) =\u003e count + 1)\n}));\n\ncountState.set(99); // Print \"New state 99\"\ncountState.increase(); // Print \"New state 100\"\n\n// The same `state` function can be reused,\n// so you don't need to set up the middleware again\nconst filterState = state(\"ALL\");\nfilterState.set(\"COMPLETED\"); // Print \"New state 'COMPLETED'\"\n```\n\n## Persist middleware\n\nYou can save the state to browser storage using the `persist` middleware.\n\n```js\nimport { stateBuilder } from \"reactish-state\";\nimport { persist } from \"reactish-state/middleware\";\n\n// Create the persist middleware,\n// optionally provide a `prefix` to prepend to the keys in storage\nconst persistMiddleware = persist({ prefix: \"myApp-\" });\nconst state = stateBuilder(persistMiddleware.middleware);\n\nconst countState = state(\n  0,\n  (set) =\u003e ({\n    increase: () =\u003e set((count) =\u003e count + 1)\n  }),\n  { key: \"count\" } // In the third parameter, assign each state a unique key\n);\nconst filterState = state(\"ALL\", null, { key: \"filter\" });\n\n// Hydrate all the states created with this middleware from storage\nuseEffect(() =\u003e {\n  // Call `hydrate` in a `useEffect` to avoid client-side mismatch,\n  // if React components are also server-rendered\n  persistMiddleware.hydrate();\n}, []);\n// You can add the `useEffect` once in your root component\n```\n\nBy default, `localStorage` is used to persist states. You can switch to `sessionStorage` or other implementations by using the `getStorage` option.\n\n```js\nconst persistMiddleware = persist({ getStorage: () =\u003e sessionStorage });\n```\n\n## Immer middleware\n\nYou can update state mutably using the `immer` middleware.\n\n```js\nimport { stateBuilder } from \"reactish-state\";\nimport { immer } from \"reactish-state/middleware/immer\";\n\nconst state = stateBuilder(immer);\n\nlet todoId = 1;\nconst todos = state([], (set) =\u003e ({\n  add: (task) =\u003e\n    set((todos) =\u003e {\n      todos.push({ id: todoId++, task, completed: false });\n      // Return the draft state for correct typing in TypeScript\n      return todos;\n    }),\n\n  toggle: (id) =\u003e\n    set((todos) =\u003e {\n      const todo = todos.find((todo) =\u003e todo.id === id);\n      if (todo) todo.completed = !todo.completed;\n    })\n}));\n\n// Use the actions\ntodos.add(\"Shop groceries\");\ntodos.toggle(1);\n```\n\n## Redux devtools middleware\n\nThis middleware provides integration with the Redux DevTools browser extension. Individual states are combined into a single object in Redux DevTools for easy inspection.\n\n```js\nimport { stateBuilder } from \"reactish-state\";\nimport { reduxDevtools } from \"reactish-state/middleware\";\n\nconst state = stateBuilder(reduxDevtools({ name: \"todoApp\" }));\n\nconst todos = state(\n  [],\n  (set) =\u003e ({\n    add: (task) =\u003e\n      set(\n        (todos) =\u003e {\n          /* Add todo */\n        },\n        // Log the action type in the second parameter of `set`\n        \"todo/add\"\n      ),\n    toggle: (id) =\u003e\n      set(\n        (todos) =\u003e {\n          /* Toggle todo */\n        },\n        // You can also log the action type along with its payload\n        { type: \"todo/toggle\", id }\n      )\n  }),\n  // Similar to the persist middleware, assign each state a unique key\n  { key: \"todos\" }\n);\n\n// `todos` and `filter` will be combined into a single object in Redux DevTools\nconst filter = state(\"ALL\", null, { key: \"filter\" });\n```\n\n## Using multiple middleware\n\nMiddleware is chainable. You can use the `applyMiddleware` utility to chain multiple middleware and pass the result to `stateBuilder`.\n\n```js\nimport { applyMiddleware } from \"reactish-state/middleware\";\n\nconst state = stateBuilder(\n  applyMiddleware([reduxDevtools(), persist().middleware, immer])\n);\n```\n\n## Using different middleware in different states\n\nThis is naturally achievable thanks to the decentralized state model.\n\n```js\nconst persistableState = stateBuilder(persist().middleware);\nconst immerState = stateBuilder(immer);\n\nconst visibilityFilter = persistableState(\"ALL\"); // Will be persisted\nconst todos = immerState([]); // Can be mutated\n```\n\nThis also eliminates the need to implement a whitelist or blacklist in the persist middleware.\n\n# Plugins\n\nWhile middleware enhances state, plugins allow you to hook into selectors. The key difference is that plugins don’t return a `set` function, as selectors are read-only. Similarly, you use the `selectorBuilder` export from the library instead of `selector`.\n\n```js\nimport { state, selectorBuilder } from \"reactish-state\";\n\nconst selector = selectorBuilder(({ get, subscribe, meta }) =\u003e {\n  subscribe(() =\u003e {\n    // Log the selector value every time it changes\n    // `meta` returns metadata of the selector\n    console.log(`${meta()} selector:`, get());\n  });\n});\n\nconst countState = state(0);\nconst doubleSelector = selector(\n  countState,\n  (count) =\u003e count * 2,\n  // Provide metadata in the last parameter to identify the selector\n  \"double\"\n);\nconst squareSelector = selector(countState, (count) =\u003e count * count, \"square\");\n\ncountState.set(5); // Logs - double selector: 10, square selector: 25\n```\n\nLikewise, there is an `applyPlugin` function for applying multiple plugins.\n\n## Redux devtools plugin\n\nIndividual selectors are combined into a single object in Redux DevTools for easy inspection.\n\n```js\nimport { selectorBuilder } from \"reactish-state\";\nimport { reduxDevtools } from \"reactish-state/plugin\";\n\nconst selector = selectorBuilder(reduxDevtools());\n// Then use the `selector` as usual...\n```\n\n# TypeScript usage\n\nThe API relies on type inference to correctly infer the types for both the value and actions of the state. There are two scenarios:\n\n## I. The type of state can be inferred from its initial value\n\nIn this case, the usage in TypeScript should be identical to JavaScript. You don't need to make any specific effort regarding typing. This is true when the state holds simple or primitive values.\n\n```ts\nconst countState = state(0, (set) =\u003e ({\n  increase: (by: number) =\u003e\n    set(\n      (count) =\u003e count + by\n      // The `count` is inferred as a number type from the initial value.\n    )\n}));\n```\n\n## II. The type of state cannot be inferred from its initial value\n\nIn this case, you have three options:\n\n### 1. Use a type assertion to specify a more specific type for the initial value:\n\n```ts\nconst myTodos = state([] as string[], (set) =\u003e ({\n  add: (newTodo: string) =\u003e set((todos) =\u003e [...todos, newTodo])\n}));\n```\n\nThis is the simplest approach since the types for custom actions will be automatically inferred.\n\n### 2. Declare the initial value separately with a specific type:\n\n```ts\nconst initialValue: string[] = [];\nconst myTodos = state(initialValue, (set) =\u003e ({\n  add: (newTodo: string) =\u003e set((todos) =\u003e [...todos, newTodo])\n}));\n```\n\nThis is basically very similar to the first method, except you need to write an additional line of code. The types for actions will be automatically inferred.\n\n### 3. Specify type parameters explicitly:\n\n```ts\nconst myTodos = state\u003cstring[], { add: (newTodo: string) =\u003e void }\u003e(\n  [],\n  (set) =\u003e ({\n    add: (newTodo) =\u003e set((todos) =\u003e [...todos, newTodo])\n  })\n);\n```\n\n# Examples\n\n- Counter – [sandbox](https://codesandbox.io/p/sandbox/reactish-counter-z42qt7) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/counter)\n- Todo app – [sandbox](https://codesandbox.io/s/reactish-todo-thyhbl) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/todo)\n- Async – [sandbox](https://codesandbox.io/s/reactish-async-2cghkg) | [source](https://github.com/szhsin/reactish-state/tree/master/examples/examples/async)\n\n# React 16/17 setup\n\nWhen using this library with React 16/17, you must set up a shim since it doesn't include a native [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore). We don't set up the shim by default to minimize the bundle size for React 18/19 users.\n\n```js\nimport { setReactShim } from \"reactish-state\";\nimport { reactShim } from \"reactish-state/shim\";\nsetReactShim(reactShim);\n```\n\nYou only need to set it up once after your app launches, outside of React code. DO NOT call `setReactShim` within any React components.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fszhsin%2Freactish-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fszhsin%2Freactish-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fszhsin%2Freactish-state/lists"}