{"id":26664450,"url":"https://github.com/evgenyifedotov/effector-reflect","last_synced_at":"2025-04-11T20:33:14.987Z","repository":{"id":55624574,"uuid":"314868242","full_name":"EvgenyiFedotov/effector-reflect","owner":"EvgenyiFedotov","description":"☄️ Render react components by effector stores.","archived":false,"fork":false,"pushed_at":"2020-12-21T19:29:00.000Z","size":397,"stargazers_count":12,"open_issues_count":4,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-06T06:08:45.445Z","etag":null,"topics":["effector","react"],"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/EvgenyiFedotov.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}},"created_at":"2020-11-21T17:40:05.000Z","updated_at":"2023-03-15T22:39:20.000Z","dependencies_parsed_at":"2022-08-15T04:50:11.319Z","dependency_job_id":null,"html_url":"https://github.com/EvgenyiFedotov/effector-reflect","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvgenyiFedotov%2Feffector-reflect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvgenyiFedotov%2Feffector-reflect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvgenyiFedotov%2Feffector-reflect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvgenyiFedotov%2Feffector-reflect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EvgenyiFedotov","download_url":"https://codeload.github.com/EvgenyiFedotov/effector-reflect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248476748,"owners_count":21110352,"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":["effector","react"],"created_at":"2025-03-25T16:28:20.241Z","updated_at":"2025-04-11T20:33:14.950Z","avatar_url":"https://github.com/EvgenyiFedotov.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Effector-reflect\n\n☄️ Render react-components by effector stores.\n\n## Install\n\n### Npm\n\n```sh\nnpm install effector-reflect\n```\n\n### Yarn\n\n```sh\nyarn add effector-reflect\n```\n\n## Motivation\n\n### Common ui\n\n```tsx\n// ./ui.ts\nimport React, { FC, ChangeEvent, useCallback } from 'react';\n\ntype InputProps = {\n  value: string;\n  onChange: ChangeEvent\u003cHTMLInputElement\u003e;\n};\n\nexport const Input: FC\u003cInputProps\u003e = ({ value, onChange }) =\u003e {\n  return \u003cinput value={value} onChange={onChange} /\u003e;\n};\n```\n\n### Old case\n\n```tsx\n// ./old-case.ts\nimport React, { FC, ChangeEvent, useCallback } from 'react';\nimport { createEvent, restore } from 'effector';\nimport { useStore } from 'effector-react';\n\nimport { Input } from './ui';\n\n// Model\nconst changeName = createEvent\u003cstring\u003e();\nconst $name = restore(changeName, '');\n\n// Component\nexport const Name: FC = () =\u003e {\n  const value = useStore($name);\n  const changed = useCallback(\n    (event: ChangeEvent\u003cHTMLInputElement\u003e) =\u003e changeName(event.target.value),\n    [],\n  );\n\n  return \u003cInput value={value} onChange={changed} /\u003e;\n};\n```\n\n### New case\n\n```tsx\n// ./new-case.ts\nimport { createEvent, restore } from 'effector';\nimport { reflect } from 'effector-reflect';\n\nimport { Input } from './ui';\n\n// Model\nconst changeName = createEvent\u003cstring\u003e();\nconst $name = restore(changeName, '');\n\n// Component\nexport const Name = reflect({\n  view: Input,\n  bind: { value: $name, onChange: (event) =\u003e changeName(event.target.value) },\n});\n```\n\n## Reflect\n\nMethod for bind stores to a view.\n\n```tsx\n// ./user.tsx\nimport React, { FC, useCallback, ChangeEvent } from 'react';\nimport { createEvent, restore } from 'effector';\nimport { reflect } from 'effector-reflect';\n\n// Base components\ntype InputProps = {\n  value: string;\n  onChange: ChangeEvent\u003cHTMLInputElement\u003e;\n  placeholder?: string;\n};\n\nconst Input: FC\u003cInputProps\u003e = ({ value, onChange, placeholder }) =\u003e {\n  return \u003cinput value={value} onChange={onChange} placeholder={placeholder} /\u003e;\n};\n\n// Model\nconst changeName = createEvent\u003cstring\u003e();\nconst $name = restore(changeName, '');\n\nconst changeAge = createEvent\u003cnumber\u003e();\nconst $age = restore(changeAge, 0);\n\nconst inputChanged = (event: ChangeEvent\u003cHTMLInputElement\u003e) =\u003e {\n  return event.currentTarget.value;\n};\n\n// Components\nconst Name = reflect({\n  view: Input,\n  bind: {\n    value: $name,\n    onChange: changeName.prepend(inputChanged),\n  },\n});\n\nconst Age = reflect({\n  view: Input,\n  bind: {\n    value: $age,\n    onChange: changeAge.prepend(parseInt).prepend(inputChanged),\n  },\n});\n\nexport const User: FC = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003cName placeholder=\"Name\" /\u003e\n      \u003cAge placeholder=\"Age\" /\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n## Create reflect\n\nMethod for creating reflect a view. So you can create a UI kit by views and use a view with a store already.\n\n```tsx\n// ./ui.tsx\nimport React, { FC, useCallback, ChangeEvent, MouseEvent } from 'react';\nimport { createReflect } from 'effector-reflect';\n\n// Input\ntype InputProps = {\n  value: string;\n  onChange: ChangeEvent\u003cHTMLInputElement\u003e;\n};\n\nconst Input: FC\u003cInputProps\u003e = ({ value, onChange }) =\u003e {\n  return \u003cinput value={value} onChange={onChange} /\u003e;\n};\n\nexport const reflectInput = createReflect(Input);\n\n// Button\ntype ButtonProps = {\n  onClick: MouseEvent\u003cHTMLButtonElement\u003e;\n  title?: string;\n};\n\nconst Button: FC\u003cButtonProps\u003e = ({ onClick, children, title }) =\u003e {\n  return (\n    \u003cbutton onClick={onClick} title={title}\u003e\n      {children}\n    \u003c/button\u003e\n  );\n};\n\nexport const reflectButton = createReflect(Button);\n```\n\n```tsx\n// ./user.tsx\nimport React, { FC } from 'react';\nimport { createEvent, restore } from 'effector';\n\nimport { reflectInput, reflectButton } from './ui';\n\n// Model\nconst changeName = createEvent\u003cstring\u003e();\nconst $name = restore(changeName, '');\n\nconst changeAge = createEvent\u003cnumber\u003e();\nconst $age = restore(changeAge, 0);\n\nconst submit = createEvent\u003cvoid\u003e();\n\n// Components\nconst Name = reflectInput({\n  value: $name,\n  onChange: (event) =\u003e changeName(event.target.value),\n});\n\nconst Age = reflectInput({\n  value: $age,\n  onChange: (event) =\u003e changeAge(parsetInt(event.target.value)),\n});\n\nconst Submit = reflectButton({\n  onClick: () =\u003e submit(),\n});\n\nexport const User: FC = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003cName /\u003e\n      \u003cAge /\u003e\n      \u003cSubmit title=\"Save left\"\u003eSave left\u003c/Submit\u003e\n      \u003cSubmit title=\"Save right\"\u003eSave right\u003c/Submit\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n## SSR\n\nFor SSR need to replace imports `effector-reflect` -\u003e `effector-reflect/ssr`.\nAlso use `event.prepend(params =\u003e params)` instead `(params) =\u003e event(params)`.\n\n```tsx\n// ./ui.tsx\nimport React, { FC, useCallback, ChangeEvent, MouseEvent } from 'react';\n\n// Input\ntype InputProps = {\n  value: string;\n  onChange: ChangeEvent\u003cHTMLInputElement\u003e;\n};\n\nconst Input: FC\u003cInputProps\u003e = ({ value, onChange }) =\u003e {\n  return \u003cinput value={value} onChange={onChange} /\u003e;\n};\n```\n\n```tsx\n// ./app.tsx\nimport React, { FC } from 'react';\nimport { createEvent, restore, Fork, createDomain } from 'effector';\nimport { reflect } from 'effector-reflect/ssr';\nimport { Provider } from 'effector-react/ssr';\n\nimport { Input } from './ui';\n\n// Model\nexport const app = createDomain();\n\nexport const changeName = app.createEvent\u003cstring\u003e();\nconst $name = restore(changeName, '');\n\n// Component\nconst Name = reflect({\n  view: Input,\n  bind: { value: $name, onChange: changeName.prepend(event =\u003e event.target.value) },\n});\n\nexport const App: FC\u003c{ data: Fork }\u003e = ({ data }) =\u003e {\n  return (\n    \u003cProvider value={data}\u003e\n      \u003cName /\u003e\n    \u003c/Provider\u003e\n  );\n};\n```\n\n```tsx\n// ./server.ts\nimport { fork, serialize, allSettled } from 'effector/fork';\n\nimport { App, app, changeName } from './app';\n\nconst render = async () =\u003e {\n  const scope = fork(app);\n\n  await allSettled(changeName, { scope, params: 'Bob' });\n\n  const data = serialize(scope);\n\n  const content = renderToString(\u003cApp data={scope} /\u003e);\n\n  return `\n    \u003cbody\u003e\n      ${content}\n      \u003cscript\u003e\n        window.__initialState__ = ${JSON.stringify(data)};\n      \u003c/script\u003e\n    \u003c/body\u003e\n  `;\n};\n```\n\n## Roadmap\n\n- [] Auto moving test from ./src to ./dist-test\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevgenyifedotov%2Feffector-reflect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevgenyifedotov%2Feffector-reflect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevgenyifedotov%2Feffector-reflect/lists"}