{"id":17380021,"url":"https://github.com/kaigorod/nirdjs","last_synced_at":"2026-04-26T15:00:46.564Z","repository":{"id":241753745,"uuid":"807557598","full_name":"kaigorod/nirdjs","owner":"kaigorod","description":"Charm is sub-atom state management library.","archived":false,"fork":false,"pushed_at":"2024-08-04T23:15:33.000Z","size":267,"stargazers_count":6,"open_issues_count":9,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-06T16:02:46.741Z","etag":null,"topics":["atom","atomic","charm","charmjs","javascript","javascript-library","jotai","react","react-state-management","reactjs","recoil","recoiljs","state-management","typescript","typescript-library"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kaigorod.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","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":"2024-05-29T10:29:18.000Z","updated_at":"2024-10-15T20:26:00.000Z","dependencies_parsed_at":"2024-08-05T00:57:48.556Z","dependency_job_id":null,"html_url":"https://github.com/kaigorod/nirdjs","commit_stats":null,"previous_names":["dmitrykaigorodov/charm","kaigorod/inert","kaigorod/charm","kaigorod/nirdjs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kaigorod/nirdjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaigorod%2Fnirdjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaigorod%2Fnirdjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaigorod%2Fnirdjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaigorod%2Fnirdjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kaigorod","download_url":"https://codeload.github.com/kaigorod/nirdjs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaigorod%2Fnirdjs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32301330,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T09:34:17.070Z","status":"ssl_error","status_checked_at":"2026-04-26T09:34:00.993Z","response_time":129,"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":["atom","atomic","charm","charmjs","javascript","javascript-library","jotai","react","react-state-management","reactjs","recoil","recoiljs","state-management","typescript","typescript-library"],"created_at":"2024-10-16T06:03:33.699Z","updated_at":"2026-04-26T15:00:46.548Z","avatar_url":"https://github.com/kaigorod.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# What is Inert\n\nInert is an atomic state management library for React.\n\nLatest documentation is available at https://jsr.io/inert/doc\n\n```jsx\n\n// counterAtom.ts \n\nconst counterAtom = atom(0);\n\nexport const useCounter = () =\u003e useValue(counterAtom);\nexport const inc = () =\u003e counterAtom.update(prev =\u003e prev + 1);\n\n// Counter.tsx\n\nconst Counter = () =\u003e {\n  const counter = useCounter();\n\n  return \u003cbutton onclick={inc}\u003e\n    clicked {counter} times\n  \u003c/button\u003e\n}\n\n```\n\n# State management best practices\n\n## Atoms stay protected\n\nNotice that `counterAtom` is not exported directly. \nThis way we avoid unexpected direct modifications of the atom we created.\n\nInstead, we create and expose micro-API to interact with the atom. \n\n## useCounter\n\n`useCounter()` is extremly simple to use: it only does two things.\n\nFirst, it returns the value of the current value of the counter.\n\nSecond, as any React hook, it subscribes the component to changes of the counter. \nSo, when a counter changes then the components re-renders. \n\n## Avoid setter callback hooks\n\nNotice, there is no `setCounter` function returned by the useValue hook. \nOne of the distiguishing features of Inert is that your Component doesn't \nhave to subscribe to the changes of the hooks. \n\nYou can update Atom value by simple functions. \nYou don't need to call the hook to create new function every render. \n\nThere several benefits to it:\n- component doesn't have to re-render if they use just the update callbacks\n- with less re-renders, the app gets better performance\n- you don't have to write `useCallback`, `useMemo` and other caching code for your update callbacks. \n- you avoid complicated cache callback bugs of `useEffect` and event listeners.\n- you write less code, and your code is more readable\n\nSo, this way the only case your components re-render is the actual \nchange of the atom they implicitly subscribed to using the `useValue` hook.\n\n### How update callbacks work internally\n\nModern state management libaries, in addition to managing front-end state have to provide compatibility \nwith Server-Side Rendering (SSR) and Server-Side Generation (SSG) rendering. In this mode, the page of \nthe application are rendered in-parallel. This way every atom sits in the memory of the node application \nin multiple instances, one per every page being rendered. A specific state of all atoms and variables\nrequired to render a single page is called Store.\n\nLet's look at the example of a todo app which uses server-side rendering. \nWhen users request of the app simultateously request their todo-list page at the same time then their todo-list pages render \nseparately and in parallel. One page and one store for every single user request. For different user the page has different data to show. \nBut, the nodejs application has single memory space where it creates atoms, functions and other variables. Store\n\nTo distinguish page renders, tools like redux, recoil, jotai use ReactContext-based approach.\nThey use ReactContext providers at the root of the render tree and then use ReactContext on the leaves of the render tree.\nThis is why they force users to create callbacks using hooks in order to pass the rendering context to the hooks.\n\nUnlike other libraries, Inert is using `AsyncLocalStorage` to pass rendering context to the callbacks and other functions. \nThis way Inert stay independent from the `ReactContext` and does not require developers to write hooks for callbacks.\n\n###\n\n\n## Best practices, enabled\n\n### Don't Repeat Yourself\n\n\n### Data consistancy\n\nWhen you don't expose raw set-state API to the world, your atoms transition from one meaningful state to the another.\nThere is no transitional or partially correct state of the atoms.\n\nNo arbitrary code is able to modify the state of your atoms. \n\nWhen you refactor you code or fix a bug, you are certain that you only have a single place to make change to or to review.\n\n### Composing interactions\n\nQuite often you need to perform multiple state changes per single action of the user. \nFor example, when user creates a new slide then they expect that:\n- (1) a new slide appears in the list of the slides\n- (2) and new slide becomes selected in the list of the slides\n\nThe code to perform these two actions together is actually simple and familiar function composition:\n\n```jsx\n// file: slide-commands.ts\n\nimport { createNewEmptyAfterCurrentSlide } from \"@state/slideList\"\nimport { switchToNextSlide } from \"@state/slideList\"\n\n\nconst createNewSlideAfterCurrentSlide = () =\u003e {\n  createNewEmptyAfterCurrentSlide();\n  switchToNextSlide();\n}\n\n```\n\n### Self-documenting functions\n\nA JavaScript function declaration is a great descriptor for the action it performs. \nMoreover, it is obvious and familiar documentation of how to use it. \n\nWhen you declare your actions as functions, you automatically get:\n- descriptive function name\n- descriptive list of parameters\n- descriptive type of parameters\n- optional, js-doc.\n\nAnd you lose this clarity when you are forced to:\n- create lists of strings for names of actions\n- implement switch/case clauses\n- write \"reducer\" code\n- wrap actions into hooks, `useMemo` or `useCallback`\n\n### Manageable modifications\n\n\n\n## Application layers\n\nFor larger project, mc recommends to split state operation and user actions code into two separate layers.\n\n\n### State actions layer\n\nThe state actions are simple JavaScript functions on top of the state atoms.\n\nThe state actions code knows nothing about user actions, DOM, UI, HTML and Components. \n\nThis layer contains all the business logic of application and it stays easy-to-test.\n\n### User interface commands\n\nUser interface commands are build on top of state actions. \n\nUser interface commands know all about the hovers, mousedowns, clicks, drags, keypresses, onChanges\nof your application and make it smooth for users.\n\nThis way, all the UI-complication doesn't get mixed up with the business logic.\n\nFor even larger projects it might make sense to move state actions of a specific business domain to a dedicated workspace package.\n\n\n# Derived atoms\n\n```jsx\n\n// word.ts \n\nconst wordAtom = atom(\"compatibility\");\nconst lettersAtom = derivedAtom(wordAtom, (word) =\u003e word.length, NeverSet)\n\nexport const useWord = () =\u003e useValue(wordAtom)\nexport const setWord = atomSetter(wordAtom)\n\nexport const useLetters = () =\u003e useValue(lettersAtom)\n\n\n// Counter.tsx\n\nconst WordAndLetters = () =\u003e {\n  const word = useWord();\n  const letters = useLetters();\n\n  return \u003c\u003e\n    \u003cp\u003e\n      \u003cinput onChange={e =\u003e setWord(e.target.value)}/\u003e\n    \u003c/p\u003e\n    \u003cp\u003e\n      Word \"{word}\" contains {letters} letters.\n    \u003c/p\u003e\n  \u003c/\u003e    \n}\n\n```\n\n## Split array atoms\n\n\n```jsx\nimport { expect, mock, test } from \"bun:test\";\nimport { splitAtom } from \"../src/arrays\";\nimport { atom, type Atom } from \"../src/inert\";\n\ntest(\"atomList does not change when single value changes\", () =\u003e {\n  const arrayAtom = atom([10, 20]);\n  const splitArrayAtom = splitAtom(arrayAtom);\n\n  const atom0: Atom\u003cnumber\u003e = splitArrayAtom.get()[0];\n  const atom1: Atom\u003cnumber\u003e = splitArrayAtom.get()[1];\n\n  const nopFn = () =\u003e { }\n  const subAtomMock = mock(nopFn as any)\n  const sub0 = mock(nopFn as any)\n  const sub1 = mock(nopFn as any)\n\n  /// test\n\n\n  splitArrayAtom.sub(subAtomMock)\n  atom0.sub(sub0);\n  atom1.sub(sub1);\n\n  atom0.set(0);\n\n  expect(sub0).toHaveBeenCalledTimes(1);\n\n  expect(sub1).toHaveBeenCalledTimes(0);\n\n  expect(subAtomMock).toHaveBeenCalledTimes(0);\n});\n\n```\n\n# Using Provider for SSR\n\n```jsx\n\nconst aAtom = atom(1);\n\nconst Comp = ({}) =\u003e {\n  const value = useValue(aAtom)\n  return \u003cbutton\u003e\n    {value}\n  \u003c/button\u003e\n}\n\n\ntest(\"render two pages at the same time\", () =\u003e {\n  const page1 = execWithAtom(createAtomStore(), () =\u003e {\n    const comp = \u003cComp /\u003e\n    aAtom.set(10);\n    return renderToString(comp)\n  })\n  const page2 = execWithAtom(createAtomStore(), () =\u003e {\n    const comp = \u003cComp /\u003e\n    aAtom.set(20);\n    return renderToString(comp)\n  })\n\n  expect(page1).toEqual(\"\u003cbutton\u003e10\u003c/button\u003e\")\n  expect(page2).toEqual(\"\u003cbutton\u003e20\u003c/button\u003e\")\n});\n\n```\n\n# Install\n\n```sh\n\nnpx jsr add inert\n\n# or\n\nbunx \n\n# or\n\ndeno add inert\n\n```\n\nThis command will add the following line to your `package.json` file\n\n```json\n{\n  //in package.json, \n  \"inert\": \"npm:@jsr/kaigorod__inert\",\n}\n```\n\n\n```\nimport { atom, atomGetter, atomSetter, useValue } from \"atom\";\n\nconst isImageSearchOnAtom = atom(false);\n\nexport const useIsImageSearchOn = () =\u003e useValue(isImageSearchOnAtom);\nexport const setIsImageSearchOn = atomSetter(isImageSearchOnAtom);\n```\n\n\n\n\n# Inspiration\nInert is inspired by recoil and jotai state management libraries.\n\n# Links\n\n- Github Repo https://github.com/kaigorod/inert\n- Deno Package https://jsr.io/inert\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaigorod%2Fnirdjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkaigorod%2Fnirdjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaigorod%2Fnirdjs/lists"}