{"id":15699234,"url":"https://github.com/cmfcmf/reacts","last_synced_at":"2025-05-09T02:28:25.088Z","repository":{"id":60954252,"uuid":"352658234","full_name":"cmfcmf/ReactS","owner":"cmfcmf","description":"React.js implemented in Squeak/Smalltalk","archived":false,"fork":false,"pushed_at":"2022-12-24T14:27:31.000Z","size":438,"stargazers_count":7,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-31T21:28:25.007Z","etag":null,"topics":["hooks","react","reactjs","smalltalk","squeak"],"latest_commit_sha":null,"homepage":"","language":"Smalltalk","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/cmfcmf.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}},"created_at":"2021-03-29T13:43:27.000Z","updated_at":"2025-03-24T10:12:48.000Z","dependencies_parsed_at":"2023-01-30T21:01:06.916Z","dependency_job_id":null,"html_url":"https://github.com/cmfcmf/ReactS","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmfcmf%2FReactS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmfcmf%2FReactS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmfcmf%2FReactS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmfcmf%2FReactS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmfcmf","download_url":"https://codeload.github.com/cmfcmf/ReactS/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253177316,"owners_count":21866306,"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":["hooks","react","reactjs","smalltalk","squeak"],"created_at":"2024-10-03T19:39:01.214Z","updated_at":"2025-05-09T02:28:25.064Z","avatar_url":"https://github.com/cmfcmf.png","language":"Smalltalk","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React/S\n\nWIP: Modern [ReactJS](https://reactjs.org), including React Hooks, implemented in Squeak/Smalltalk.\n\n## Implementation Status\n\n\n- [x] Node Types\n  - [x] String\n  - [x] Text\n  - [x] nil\n  - [x] SequenceableCollection\n  - [x] Components\n  - [x] Morph (via CMFReactNodeMorph)\n  - [x] Fragment (via Array)\n- [x] [Fragments](https://reactjs.org/docs/fragments.html)\n- [x] [memo](https://reactjs.org/docs/react-api.html#reactmemo)\n- [ ] [Refs](https://reactjs.org/docs/refs-and-the-dom.html)\n- [x] [Keys](https://reactjs.org/docs/lists-and-keys.html)\n- [ ] Hooks\n  - [x] [useState](https://reactjs.org/docs/hooks-state.html)\n  - [x] [useEffect](https://reactjs.org/docs/hooks-effect.html)\n  - [x] [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext)\n  - [x] [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer)\n  - [x] [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)\n  - [x] [useMemo](https://reactjs.org/docs/hooks-reference.html#usememo)\n  - [x] [useRef](https://reactjs.org/docs/hooks-reference.html#useref)\n  - [ ] [useLayoutEffect](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) (not sure if this is even needed)\n  - [ ] [useImperativeHandle](https://reactjs.org/docs/hooks-reference.html#useimperativehandle)\n  - [ ] [useDebugValue](https://reactjs.org/docs/hooks-reference.html#usedebugvalue)\n- [x] Fast Refresh (refresh UI on source code changes without losing its state)\n\n## Installation\n\n```smalltalk\nMetacello new\n  baseline: 'CmfcmfReact';\n  repository: 'github://cmfcmf/ReactS:main/packages';\n  load: #default.\n```\n\n## Demo\n\n```smalltalk\nCMFReactComponentDemo openInHand\n```\n\n## Examples\n\n### Simple Click Counter\n\nA simple example component that provides a button to increment a number is shown below:\n\n```smalltalk\nMyExampleComponent \u003e\u003e render: props\n\n\t| clicks |\n\tclicks := self useState: 0.\n\t\n\t^ CMFReactComponentContainer asReactNodeBuilder children: {\n\t\tclicks get.\n\t\tCMFReactComponentContainer asReactNodeBuilder props: {#direction -\u003e #leftToRight}; children: {\n\t\t\tCMFReactComponentButton asReactNodeBuilder props: {\n\t\t\t\t#onClick -\u003e [clicks set: [:oldClicks | oldClicks + 1]].\n\t\t\t\t#label -\u003e 'increment'}; build.\n\t\t\tCMFReactComponentButton asReactNodeBuilder props: {\n\t\t\t\t#onClick -\u003e [clicks set: 0].\n\t\t\t\t#label -\u003e 'reset'}; build.\n\t\t}; build.\n\t}; build\n```\n\n`MyExampleComponent` must inherit from `CMFReactComponent`. To render and open it, execute `MyExampleComponent openInHand`\n\n### TODO Notes App\n\nA window that manages a list of TODO items (`CMFReactExampleAppTodoNotes`).\n\n```smalltalk\nCMFReactExampleAppTodoNotes \u003e\u003e render: props\n\n\t| todos nextId selectedId selectedTodo |\n\tnextId := self useRef: 1.\n\ttodos := self\n\t\tuseReducer: [:state :action |\n\t\t\taction first caseOf: {\n\t\t\t\t[#add] -\u003e [ | id title |\n\t\t\t\t\ttitle := (UIManager default request: 'Title') ifNil: [''].\n\t\t\t\t\ttitle \n\t\t\t\t\t\tifEmpty: [state] \n\t\t\t\t\t\tifNotEmpty: [| content |\n\t\t\t\t\t\t\tcontent := (UIManager default request: 'Content') ifNil: [''].\n\t\t\t\t\t\t\tid := nextId get.\n\t\t\t\t\t\t\tnextId set: id + 1.\n\t\t\t\t\t\t\tstate copyWith: (Dictionary newFrom: {#id -\u003e id. #title -\u003e title. #content -\u003e content})]].\n\t\t\t\t[#delete] -\u003e [ | id |\n\t\t\t\t\tid := action second.\n\t\t\t\t\tstate copyWithout: (state detect: [:oldTodo | (oldTodo at: #id) == id])]}]\n\t\tinitialState: {}.\n\t\n\tselectedId := self useState: 0.\n\tselectedTodo := todos get detect: [:each | (each at: #id) == selectedId get] ifNone: [nil].\n\t\n\t^ CMFReactComponentWindow asReactNodeBuilder\n\t\tprops: {#label -\u003e 'TODO Notes'. #defaultExtent -\u003e (600 @ 300)};\n\t\tchildren: {\n\t\t\t(#Sidebar asReactNodeBuilder: self)\n\t\t\t\tprops: {#todos -\u003e todos get. #todosDispatch -\u003e todos dispatch. #setSelectedId -\u003e selectedId setter};\n\t\t\t\tbuild.\n\t\t\tCMFReactComponentContainer asReactNodeBuilder\n\t\t\t\tprops: {#scrollable -\u003e true. #layoutFrame -\u003e (LayoutFrame fractions: (0.3 @ 0 corner: 1 @ 1))};\n\t\t\t\tchildren: {\n\t\t\t\t\tselectedTodo\n\t\t\t\t\t\tifNil: ['-- nothing selected --']\n\t\t\t\t\t\tifNotNil: [{selectedTodo at: #title. selectedTodo at: #content}]};\n\t\t\t\tbuild};\n\t\tbuild\n\n\nCMFReactExampleAppTodoNotes \u003e\u003e Sidebar: props\n\t\n\t\u003cmemoize\u003e\n\t\n\t| todos todosDispatch setSelectedId |\n\ttodos := props at: #todos.\n\ttodosDispatch := props at: #todosDispatch.\n\tsetSelectedId := props at: #setSelectedId.\n\t\n\t^ CMFReactComponentContainer asReactNodeBuilder\n\t\tprops: {\n\t\t\t#layoutFrame -\u003e (LayoutFrame fractions: (0@0 extent: 0.3@1))};\n\t\tchildren: {\n\t\t\tCMFReactComponentContainer asReactNodeBuilder\n\t\t\t\tprops: {#layoutFrame -\u003e (LayoutFrame fractions: (0@0 extent: 1@0) offsets: (0@0 extent: 0@20))};\n\t\t\t\tchildren: {\n\t\t\t\t\tCMFReactComponentButton asReactNodeBuilder\n\t\t\t\t\t\tprops: {#label -\u003e 'Add TODO Note'. #onClick -\u003e [todosDispatch value: {#add}]};\n\t\t\t\t\t\tbuild};\n\t\t\t\tbuild.\n\t\t\tCMFReactComponentContainer asReactNodeBuilder\n\t\t\t\tprops: {\n\t\t\t\t\t#scrollable -\u003e true.\n\t\t\t\t\t#layoutFrame -\u003e (LayoutFrame fractions: (0@0 extent: 1@1) offsets: (0@20 extent: 0@ -20))};\n\t\t\t\tchildren: {\n\t\t\t\t\ttodos collect: [:each |\n\t\t\t\t\t\t(#TODOSidebarItem asReactNodeBuilder: self)\n\t\t\t\t\t\t\tprops: {#key -\u003e (each at: #id). #todo -\u003e each. #dispatch -\u003e todosDispatch. #setSelectedId -\u003e setSelectedId};\n\t\t\t\t\t\t\tbuild]};\n\t\t\t\tbuild};\n\t\tbuild.\n\n\nCMFReactExampleAppTodoNotes \u003e\u003e TODOSidebarItem: props\n\n\t\u003cmemoize\u003e\n\t\n\t| todo |\n\ttodo := props at: #todo.\n\n\t^ CMFReactComponentContainer asReactNodeBuilder\n\t\tchildren: {\n\t\t\ttodo at: #title.\n\t\t\tCMFReactComponentButton asReactNodeBuilder\n\t\t\t\tprops: {#label -\u003e 'select'. #onClick -\u003e [(props at: #setSelectedId) value: (todo at: #id)]};\n\t\t\t\tbuild.\n\t\t\tCMFReactComponentButton asReactNodeBuilder\n\t\t\t\tprops: {#label -\u003e 'delete'. #onClick -\u003e [(props at: #dispatch) value: {#delete. todo at: #id}]};\n\t\t\t\tbuild};\n\t\tbuild\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmfcmf%2Freacts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmfcmf%2Freacts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmfcmf%2Freacts/lists"}