WIP: Modern ReactJS, including React Hooks, implemented in Squeak/Smalltalk.
- Node Types
- String
- Text
- nil
- SequenceableCollection
- Components
- Morph (via CMFReactNodeMorph)
- Fragment (via Array)
- Fragments
- memo
- Refs
- Keys
- Hooks
- useState
- useEffect
- useContext
- useReducer
- useCallback
- useMemo
- useRef
- useLayoutEffect (not sure if this is even needed)
- useImperativeHandle
- useDebugValue
- Fast Refresh (refresh UI on source code changes without losing its state)
Metacello new
baseline: 'CmfcmfReact';
repository: 'github://cmfcmf/ReactS:main/packages';
load: #default.
CMFReactComponentDemo openInHand
A simple example component that provides a button to increment a number is shown below:
MyExampleComponent >> render: props
| clicks |
clicks := self useState: 0.
^ CMFReactComponentContainer asReactNodeBuilder children: {
clicks get.
CMFReactComponentContainer asReactNodeBuilder props: {#direction -> #leftToRight}; children: {
CMFReactComponentButton asReactNodeBuilder props: {
#onClick -> [clicks set: [:oldClicks | oldClicks + 1]].
#label -> 'increment'}; build.
CMFReactComponentButton asReactNodeBuilder props: {
#onClick -> [clicks set: 0].
#label -> 'reset'}; build.
}; build.
}; build
MyExampleComponent
must inherit from CMFReactComponent
. To render and open it, execute MyExampleComponent openInHand
A window that manages a list of TODO items (CMFReactExampleAppTodoNotes
).
CMFReactExampleAppTodoNotes >> render: props
| todos nextId selectedId selectedTodo |
nextId := self useRef: 1.
todos := self
useReducer: [:state :action |
action first caseOf: {
[#add] -> [ | id title |
title := (UIManager default request: 'Title') ifNil: [''].
title
ifEmpty: [state]
ifNotEmpty: [| content |
content := (UIManager default request: 'Content') ifNil: [''].
id := nextId get.
nextId set: id + 1.
state copyWith: (Dictionary newFrom: {#id -> id. #title -> title. #content -> content})]].
[#delete] -> [ | id |
id := action second.
state copyWithout: (state detect: [:oldTodo | (oldTodo at: #id) == id])]}]
initialState: {}.
selectedId := self useState: 0.
selectedTodo := todos get detect: [:each | (each at: #id) == selectedId get] ifNone: [nil].
^ CMFReactComponentWindow asReactNodeBuilder
props: {#label -> 'TODO Notes'. #defaultExtent -> (600 @ 300)};
children: {
(#Sidebar asReactNodeBuilder: self)
props: {#todos -> todos get. #todosDispatch -> todos dispatch. #setSelectedId -> selectedId setter};
build.
CMFReactComponentContainer asReactNodeBuilder
props: {#scrollable -> true. #layoutFrame -> (LayoutFrame fractions: (0.3 @ 0 corner: 1 @ 1))};
children: {
selectedTodo
ifNil: ['-- nothing selected --']
ifNotNil: [{selectedTodo at: #title. selectedTodo at: #content}]};
build};
build
CMFReactExampleAppTodoNotes >> Sidebar: props
<memoize>
| todos todosDispatch setSelectedId |
todos := props at: #todos.
todosDispatch := props at: #todosDispatch.
setSelectedId := props at: #setSelectedId.
^ CMFReactComponentContainer asReactNodeBuilder
props: {
#layoutFrame -> (LayoutFrame fractions: (0@0 extent: 0.3@1))};
children: {
CMFReactComponentContainer asReactNodeBuilder
props: {#layoutFrame -> (LayoutFrame fractions: (0@0 extent: 1@0) offsets: (0@0 extent: 0@20))};
children: {
CMFReactComponentButton asReactNodeBuilder
props: {#label -> 'Add TODO Note'. #onClick -> [todosDispatch value: {#add}]};
build};
build.
CMFReactComponentContainer asReactNodeBuilder
props: {
#scrollable -> true.
#layoutFrame -> (LayoutFrame fractions: (0@0 extent: 1@1) offsets: (0@20 extent: 0@ -20))};
children: {
todos collect: [:each |
(#TODOSidebarItem asReactNodeBuilder: self)
props: {#key -> (each at: #id). #todo -> each. #dispatch -> todosDispatch. #setSelectedId -> setSelectedId};
build]};
build};
build.
CMFReactExampleAppTodoNotes >> TODOSidebarItem: props
<memoize>
| todo |
todo := props at: #todo.
^ CMFReactComponentContainer asReactNodeBuilder
children: {
todo at: #title.
CMFReactComponentButton asReactNodeBuilder
props: {#label -> 'select'. #onClick -> [(props at: #setSelectedId) value: (todo at: #id)]};
build.
CMFReactComponentButton asReactNodeBuilder
props: {#label -> 'delete'. #onClick -> [(props at: #dispatch) value: {#delete. todo at: #id}]};
build};
build