{"id":15046437,"url":"https://github.com/minuukang/react-normalize-entity-station","last_synced_at":"2025-10-27T01:30:37.202Z","repository":{"id":124585590,"uuid":"371594864","full_name":"minuukang/react-normalize-entity-station","owner":"minuukang","description":"📦️ Simple normalize entity system use with react hook!","archived":false,"fork":false,"pushed_at":"2021-10-13T13:25:40.000Z","size":351,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-01T01:25:31.844Z","etag":null,"topics":["immer","jotai","moxb","normalizr","react","zustand"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/minuukang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2021-05-28T05:54:37.000Z","updated_at":"2023-08-23T18:22:56.000Z","dependencies_parsed_at":"2023-08-10T06:00:19.229Z","dependency_job_id":null,"html_url":"https://github.com/minuukang/react-normalize-entity-station","commit_stats":{"total_commits":27,"total_committers":1,"mean_commits":27.0,"dds":0.0,"last_synced_commit":"32218a22faf5a82c93e4820f97a404865475cb93"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minuukang%2Freact-normalize-entity-station","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minuukang%2Freact-normalize-entity-station/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minuukang%2Freact-normalize-entity-station/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minuukang%2Freact-normalize-entity-station/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/minuukang","download_url":"https://codeload.github.com/minuukang/react-normalize-entity-station/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238418229,"owners_count":19468869,"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":["immer","jotai","moxb","normalizr","react","zustand"],"created_at":"2024-09-24T20:53:06.443Z","updated_at":"2025-10-27T01:30:36.860Z","avatar_url":"https://github.com/minuukang.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React entity normalize station\n\n**Simple and easy, type safe** normalize \u0026 denormalize entity building system with [`normalizr`](https://github.com/paularmstrong/normalizr), [`jotai`](https://github.com/pmndrs/jotai), [`zustand`](https://github.com/pmndrs/zustand) and react hook!\n\n| ✅ In this document; we call **EntityStation** is about build system(`configureNormalizeEntityStation`) result!\n\n## Install\n\n```bash\nnpm i react-entity-normalize-station --save\n```\n\n## Example\n\n### Interface\n\n```ts\nexport interface User {\n  id: number;\n  name: string;\n}\n\nexport interface Comment {\n  id: number;\n  body: string;\n  author: User;\n  preview_replys?: Comment[];\n}\n\nexport interface Post {\n  id: number;\n  body: string;\n  author: User;\n  likeCount: number;\n  hasMyLike: boolean;\n  preview_comments?: Comment[];\n}\n```\n\n### Configure **EntityStation**\n\n```ts\n// src/entity-station.ts\nimport { configureNormalizeEntityStation, createEntityModel } from 'react-entity-normalize-station';\n\nconst users = createEntityModel\u003cUser\u003e('users')();\nconst comments = createEntityModel\u003cComment\u003e('comments')(self =\u003e ({\n  preview_replys: [self], // example of recursion using\n  author: users\n}));\nconst posts = createEntityModel\u003cPost\u003e('posts')({\n  author: users,\n  preview_comments: [comments]\n});\n\nexport const {\n  useDenormalize,\n  useEntitys,\n  useNormalizeEntity,\n  useNormalizeHandler\n} = configureNormalizeEntityStation({\n  users,\n  comments,\n  posts\n});\n```\n\n### Usage (fetch data with react-query)\n\n```tsx\nimport {\n  useDenormalize,\n  useEntitys,\n  useNormalizeEntity,\n  useNormalizeHandler\n} from './entity-station';\nimport { useQuery, useMutation } from 'react-query';\n\ndeclare const fetchPosts: () =\u003e Promise\u003cPost[]\u003e;\ndeclare const toggleLikePost: (postId: number) =\u003e Promise\u003cvoid\u003e;\n\nfunction Post ({ postId }: { postId: number; }) {\n  const [post, setPost] = useDenormalize('posts', postId);\n  function toggleLike () {\n    setPost({\n      ...post,\n      likeCount: post.likeCount + (post.hasMyLike ? -1 : 1),\n      hasMyLike: !post.hasMyLike\n    });\n  }\n  const likeMutation = useMutation(() =\u003e toggleLikePost(post.id), {\n    onMutate: toggleLike,\n    onError: toggleLike\n  });\n  return (\n    \u003cdiv\u003e\n      {post.body}\n      \u003cinput\n        type=\"checkbox\"\n        checked={post.hasMyLike}\n        onChange={() =\u003e likeMutation.mutate()}\n      /\u003e\n      (Like : {post.likeCount})\n    \u003c/div\u003e\n  )\n}\n\nexport default Example () {\n  const postQuery = useQuery('postQuery', fetchPosts, {\n    suspense: true\n  });\n  const [posts] = useNormalizeEntity('posts', postQuery.data);\n  return (\n    \u003cdiv\u003e\n      {posts.map(post =\u003e \u003cPost post={post} key={post.id} /\u003e)}\n    \u003c/div\u003e\n  )\n}\n```\n\n## MobX (`mobx-react-lite`) integration\n\nYou can use this package with `mobx-react-lite`,  [`useLocalObservable`](https://github.com/mobxjs/mobx-react#uselocalobservable-hook) to connect entity station.\n\n**Step 1**, import `react-entity-normalize-station/mobx` to configure **EntityStation**! It will be add `useEntityObservable` at the default entity station\n\n```ts\nimport { configureNormalizeEntityStation } from 'react-entity-normalize-station/mobx';\n\n// ... Model setting (users, comments, posts) ...\n\nexport const { useEntityObservable } = configureNormalizeEntityStation({\n  users,\n  comments,\n  posts\n});\n```\n\n**Step 2**, You can use `useEntityObservable` similar like `useLocalObservable`. But be explicit what property is connect to normalize entity. Typescript will help property setting.\n\n```ts\ninterface PostStoreInterface {\n  currentPosts: Posts[];\n  fetchPosts(): Promise\u003cvoid\u003e;\n}\n\nconst StoreProvider = () =\u003e {\n  // const postStore = useLocalObservable(PostStore);\n  const postStore = useEntityObservable(PostStore, {\n    currentPosts: 'posts'\n  });\n  // ... set context provider\n};\n```\n\nNow, the **EntityStation** should intercept original property to change computed value. you can preservation your componentcode base to change it!\n\n### ⚠️ **Warning** to use MobX integration\n\nWhen using this integration, entity station will change observable value to computed value. That mean you can't use MobX magicall syntax. Let's find out migrate mobx store.\n\n#### Using array case\n\nWhen you want update the array, the reference will be change.\n\n```ts\n// Before\nrunInAction(() =\u003e {\n  this.posts.push(newPost);\n});\n\n// After\nrunInAction(() =\u003e {\n  this.posts = [...this.posts, newPost];\n});\n```\n\n#### Using object case\n\nWhen you want update the object, reference will be change or just use `normalize` or `produceEntity` from **EntityStation**.\n\n```ts\n// Before\nrunInAction(() =\u003e {\n  this.post.isLike = true;\n});\n\n// After\nimport { normalize, produceEntity } from 'src/entity-station.ts';\n\nrunInAction(() =\u003e {\n  this.post = {\n    ...this.post,\n    isLike: true\n  }\n  // or\n  normalize('posts', {\n    ...this.post,\n    isLike: true\n  });\n  // or\n  produceEntity('posts', posts =\u003e {\n    const post = posts[this.post.id];\n    if (post) {\n      post.isLike = true;\n    }\n  });\n});\n```\n\n## API\n### `createEntityModel\u003cT\u003e(name): (definition, options) =\u003e Entity`\n\nThis function is type safe version of [`normalizr(new entity.Schema)`](https://github.com/paularmstrong/normalizr/blob/master/docs/api.md#entitykey-definition---options--). It will be use partial function to set model type at first generic, definition and options type inference at second function.\n\n```ts\n// src/entity.ts\nimport { createEntityModel } from 'react-entity-normalize-station';\n\nconst users = createEntityModel\u003cUser\u003e('users')({}, {\n  idAttribute: 'userId'\n});\nconst comments = createEntityModel\u003cComment\u003e('comments')(self =\u003e ({\n  author: users\n  reply_preview: [self]\n}));\n```\n\n### Type `EntityRecord`\n\nThis type is interface of `entityStore` data. If you want to use this type at your project, can use like,\n\n```ts\n// src/entity.ts\nimport type { EntityRecord } from 'react-entity-normalize-station';\n\nconst models = { users, comments }; // result of createEntityModel\n\nexport type AppEntityRecord = EntityRecord\u003ctypeof models\u003e;\n```\n\n### Type `EntityModel`\n\nThis type is record of entity model. If you want to use this type at your project, can use like,\n\n```ts\n// src/entity.ts\nimport type { EntityModel } from 'react-entity-normalize-station';\n\nconst models = { users, comments }; // result of createEntityModel\n\nexport type AppEntityModel = EntityModel\u003ctypeof models\u003e;\n\n// ...\ntype UserTypeFromAppEntityModel = AppEntityModel['users']; // User\ntype CommentTypeFromAppEntityModel = AppEntityModel['comments']; // User\n```\n\n### `configureNormalizeEntityStation(models) =\u003e EntityStation`\n\nThis function build **EntityStation**. It's create entity store, normalize \u0026 denormalize functions, entity producer, react custom hooks, initail provider.\n\n```ts\n// src/entity-station.ts\nimport { configureNormalizeEntityStation } from 'react-entity-normalize-station';\n\nexport const {\n  entityStore,\n  normalize,\n  denormalize,\n  produceEntity,\n  useNormalizeEntity,\n  useDenormalize,\n  useEntitys,\n  NormalizeEntityProvider\n} = configureNormalizeEntityStation({\n  users,\n  comments\n});\n```\n\n#### EntityStation.`entityStore`\n\nThis store create by [`zustand/vanilla`](https://github.com/pmndrs/zustand#using-zustand-without-react). If you want to get state of entity record, subscribe entity change without react, use this store.\n\n```ts\nimport { entityStore } from 'src/entity-station.ts';\n\nconst unsubscriber = entityStore.subsribe(entities =\u003e {\n  console.debug('Entity update', { entities });\n  // or console.debug('Entity update', { entities: entityStore.getState() });\n});\n```\n\n#### EntityStation.`normalize(modelKey: EntityKey, data: Model | Model[]) =\u003e IdType | IdType[]`\n\n```ts\nimport { normalize } from 'src/entity-station.ts';\n\ndeclare const MOCK_COMMENT: Comment; // { id: number }\ndeclare const MOCK_USER: User; // { userId: string }\n\n// When model id is number\nconst commentId = normalize('comments', MOCK_COMMENT); // return `number`\nconst commentIds = normalize('comments', [MOCK_COMMENT]); // return `number[]`\n\n// When model id is string\nconst userId = normalize('users', MOCK_USER); // return `string`\nconst userIds = normalize('users', [MOCK_USER]); // return `string[]`\n```\n\n* This function work update normalize entity and return id.\n* Normalize entity will update `entityStore`.\n* This function is type safe of infer Model \u0026 id type\n\n#### EntityStation.`denormalize(modelKey: EntityKey, data: IdType | IdType[]) =\u003e (Model | undefined) | Model[]`\n\n```ts\nimport { denormalize } from 'src/entity-station.ts';\n\n// When model id is number\nconst comment = denormalize('comments', 1); // return `Comment | undefined`\nconst comments = denormalize('comments', [1]); // return `Comment[]`\n\n// When model id is string\nconst user = denormalize('users', 'asdf'); // return `User | undefined`\nconst users = denormalize('users', ['asdf']); // return `User[]`\n```\n\n* This function work denormalize entity store and parameter data\n* This function is type safe of infer Model \u0026 id type\n\n#### EnityStation.`produceEntity(modelKey: EntityKey, callback: (records: Record\u003cIdType, NormalizeModel\u003e) =\u003e unknown) =\u003e void`\n\nThis function is produce entity record by [`immer`](https://immerjs.github.io/immer/).\n\n```ts\nimport { produceEntity } from 'src/entity-station.ts';\n\nproduceEntity('comments', comments =\u003e {\n  const comment = comments[1];\n  if (comment) {\n    comment.isLike = true;\n    comment.likeCount ++;\n  }\n});\n```\n\n#### EntityStation.`useNormalizeEntity(modelKey: EntityKey, data: Model | Model[]) =\u003e [(Model | undefined) | Model[]), setter]`\n\nThis function similar by EntityStation.`normalize`, but will return denormalize result. You can use at react function component and set the data\n\n```tsx\nimport { useNormalizeEntity } from 'src/entity-station.ts';\n\nfunction CommentList () {\n  const commentResult = useSuspenseFetch('https://.../comments'); // This hook is psuedo of fetching data suspense \u0026 refresh\n  const [comments, setComments] = useNormalizeEntity('comments', commentResult.data);\n  const handleRefreshComment = useCallback(async () =\u003e {\n    await commentResult.refresh();\n    setComments(commentResult.data);\n  }, []);\n  return (\n    \u003cdiv\u003e\n      {comments.map(comment =\u003e (\n        \u003cComment comment={comment} key={comment.id} /\u003e\n      ))}\n      \u003cbutton onClick={handleRefreshComment}\u003eRefresh\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n#### EntityStation.`useDenormalize(modelKey: EntityKey, data: IdType | IdType[]) =\u003e [(Model | undefined) | Model[]), setter]`\n\nThis function similar by EntityStation.`denormalize`. You can use at react function component and set the data\n\n```tsx\nimport { useDenormalize } from 'src/entity.ts';\n\nfunction CommentLikeButton ({ commentId }: { commentId: number }) {\n  const [comment, setComment] = useDenormalize('comments', commentId);\n  const handleToggleLike = useCallback(() =\u003e {\n    setComment({\n      ...comment,\n      isLike: !comment.isLike,\n      likeCount: comment.likeCount + (comment.isLike ? -1 : 1)\n    });\n  }, [comment, setComment]);\n  return (\n    \u003cCommentLikeIcon\n      isLike={comment.isLike}\n      onClick={handleToggleLike}\n    /\u003e\n  );\n}\n```\n\n#### EnityStation.`useEntitys(): EntityRecords`\n\nIf you want to use entity records at react hook, use this!\n\n```ts\nimport { useEntitys } from 'src/entity.ts';\n\nfunction useEntityUpdateDebug () {\n  const entities = useEntitys();\n  useEffect(() =\u003e {\n    console.debug('Entity update!', { entities });\n  }, [entities]);\n}\n```\n\n#### EntityStation.`NormalizeEntityProvider({ initialEntityRecord: EntityRecord }): React.ComponentType`\n\nIf you want to initial entity store(like using ssr), using this provider. This provider is not required when you don't need initial entity at your app.\n\n```tsx\nimport { NormalizeEntityProvider } from 'src/entity.ts';\n\nfunction App(props) {\n  return (\n    \u003cNormalizeEntityProvider initialEntityRecord={INITIAL_ENITY_RECORD}\u003e\n      {props.children}\n    \u003c/NormalizeEntityProvider\u003e\n  );\n}\n```\n\n#### EntityStation.`subscribe(modelKey: EntityKey, data: IdType | IdType[]): Unsubscriber(() =\u003e void)`\n\nIf you want to subscribe change model data, you can use this subscriber. Return type is subscriber function.\nThis function use at mobx integration.\n\n```tsx\nimport { subscribe } from 'src/entity.ts';\n\nsubscribe('comments', [1, 2, 3], () =\u003e {\n  console.log('Comment [1, 2, 3] is changed!');\n});\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminuukang%2Freact-normalize-entity-station","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fminuukang%2Freact-normalize-entity-station","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminuukang%2Freact-normalize-entity-station/lists"}