Skip to content

Commit 8bda0f2

Browse files
working on react
1 parent 188b50c commit 8bda0f2

File tree

7 files changed

+2314
-2562
lines changed

7 files changed

+2314
-2562
lines changed

playground/internal/playground/playground.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ func (p *Playground) onCodeInput() {
7979
func (p *Playground) currentIndent() string {
8080
start, _ := p.code.GetSelection()
8181
code := p.code.GetCode()
82+
fmt.Printf("Code len: %d, start: %d\n", len(code), start) // TODO(grantnelson-wf): REMOVE
83+
8284
par := strings.LastIndex(code[:start], "\n") + 1
8385
i := par
8486
for i < start {
@@ -153,6 +155,7 @@ func (p *Playground) onFormat() {
153155
p.output.AddError(err)
154156
return
155157
}
158+
out = append(out, []byte("// FORMATTED\n")...) // TODO(grantnelson-wf): REMOVE
156159
p.code.SetCode(string(out))
157160
}
158161

playground/internal/react/banner.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ var _ Component = (*banner)(nil)
2828

2929
func NewBanner() common.Banner {
3030
return &banner{
31-
importChecked: NewState(true),
32-
shareUrl: NewState(``),
33-
shareUrlVisible: NewState(false),
34-
version: NewState(`vx.x.x`),
35-
3631
// We might be inside an iframe, but want to use the location of topmost window.
3732
location: dom.GetWindow().Top().Location(),
3833
}
@@ -89,21 +84,21 @@ func (b *banner) SetVersion(version string) {
8984
}
9085

9186
func (b *banner) Render(props Props) *Element {
92-
b.importChecked.Use()
93-
b.shareUrl.Use()
94-
b.shareUrlVisible.Use()
95-
b.version.Use()
87+
b.importChecked = UseState(true)
88+
b.shareUrl = UseState(``)
89+
b.shareUrlVisible = UseState(false)
90+
b.version = UseState(`vx.x.x`)
9691
b.shareUrlRef = UseRef()
9792

9893
UseEffect(func() {
9994
if b.shareUrlVisible.Get() {
10095
b.shareUrlRef.Call(`select`)
10196
}
102-
}, b.shareUrlVisible, b.shareUrlRef)
97+
}, []any{b.shareUrlVisible, b.shareUrlRef})
10398

104-
return Div(Props{
99+
return Div(props.Add(Props{
105100
`id`: `banner`,
106-
}, Span(Props{
101+
}), Span(Props{
107102
`id`: `head`,
108103
}, `playground `,
109104
CreateElement(`small`, nil, `GopherJS (`, b.version.Get(), `)`),

playground/internal/react/bindings.go

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package react
22

33
import (
4-
"fmt"
4+
"errors"
55

66
"github.com/gopherjs/gopherjs/js"
77
)
@@ -29,18 +29,12 @@ type (
2929
Props map[string]any
3030

3131
// State is a value managed by React's useState hook.
32-
// If the component function hasn't been called yet,
33-
// the initial value is used and updated until the component is rendered.
34-
// After the component is rendered, the value is managed by React.
3532
// See: https://react.dev/reference/react/useState
3633
//
3734
// NOTE: Some types like `State[int]` must instead be `State[float64]`,
3835
// and `State[[]*foo]` must instead be `State[[]any]`, because of
3936
// how the underlying React hook works.
40-
State[T any] struct {
41-
initial T
42-
getter, setter *js.Object
43-
}
37+
State[T any] struct{ getter, setter *js.Object }
4438

4539
// Ref is a React ref created with UseRef().
4640
// It is a mutable object with a `current` property that can hold any value.
@@ -63,6 +57,11 @@ type (
6357
// See: https://react.dev/reference/react-dom/client/createRoot
6458
)
6559

60+
var (
61+
ErrStateNotInitialized = errors.New("react: State not initialized")
62+
ErrRefNotInitialized = errors.New("react: Ref not initialized")
63+
)
64+
6665
func reactDom() *js.Object { return js.Global.Get(`ReactDOM`) }
6766
func react() *js.Object { return js.Global.Get(`React`) }
6867

@@ -128,51 +127,38 @@ func Button(value string, props Props, onClick func(e *js.Object)) *Element {
128127
return CreateElement(`input`, props)
129128
}
130129

131-
// NewState creates a new State with the given initial value.
132-
// The initial value is only used before the component function is called
133-
// for the first time. After that, the value is managed by React.
134-
// See: https://react.dev/reference/react/useState
135-
func NewState[T any](initial T) *State[T] {
136-
fmt.Printf("NewState called [%T]\n", initial) // TODO(grantnelson-wf): REMOVE
137-
return &State[T]{initial: initial}
130+
// Add merges another Props into this one.
131+
func (p Props) Add(other Props) Props {
132+
if p == nil {
133+
p = Props{}
134+
}
135+
for k, v := range other {
136+
p[k] = v
137+
}
138+
return p
138139
}
139140

140-
// Use initializes the state for the current component render.
141+
// UseState creates a state for the current component render.
141142
// It must be called unconditionally at the top level of the component function.
142-
// This takes the place of calling React's UseState so that the state can be
143-
// created outside of the component function.
144143
// See: https://react.dev/reference/react/useState
145-
func (s *State[T]) Use() {
146-
fmt.Printf("Use called [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
147-
r := react().Call(`useState`, s.initial)
148-
s.getter = r.Index(0)
149-
s.setter = r.Index(1)
144+
func UseState[T any](initial T) *State[T] {
145+
r := react().Call(`useState`, initial)
146+
return &State[T]{getter: r.Index(0), setter: r.Index(1)}
150147
}
151148

152149
func (s *State[T]) Get() T {
153-
if s != nil {
154-
if s.getter != nil {
155-
fmt.Printf("Get called via getter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
156-
return s.getter.Interface().(T)
157-
}
158-
fmt.Printf("Get called without getter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
159-
return s.initial
150+
if s != nil && s.getter != nil {
151+
return s.getter.Interface().(T)
160152
}
161-
var zero T
162-
fmt.Printf("Get called on nil [%T]\n", zero) // TODO(grantnelson-wf): REMOVE
163-
return zero
153+
panic(ErrStateNotInitialized)
164154
}
165155

166156
func (s *State[T]) Set(v T) {
167-
if s != nil {
168-
if s.setter != nil {
169-
fmt.Printf("Set called with setter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
170-
s.setter.Invoke(v)
171-
return
172-
}
173-
fmt.Printf("Set called without setter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
174-
s.initial = v
157+
if s != nil && s.setter != nil {
158+
s.setter.Invoke(v)
159+
return
175160
}
161+
panic(ErrStateNotInitialized)
176162
}
177163

178164
// UseRef creates a mutable ref object that persists for the lifetime of the component.
@@ -183,7 +169,10 @@ func UseRef() *Ref {
183169
}
184170

185171
func (r *Ref) Current() *js.Object {
186-
return r.holder.Get(`current`)
172+
if r != nil && r.holder != nil {
173+
return r.holder.Get(`current`)
174+
}
175+
panic(ErrRefNotInitialized)
187176
}
188177

189178
func (r *Ref) Get(key string) *js.Object {
@@ -198,10 +187,34 @@ func (r *Ref) Call(name string, args ...any) {
198187
r.Current().Call(name, args...)
199188
}
200189

190+
// UseEffectNoDeps registers an effect function that is called after every render.
191+
// See: https://react.dev/reference/react/useEffect
192+
func UseEffectNoDeps(effect func()) {
193+
react().Call(`useEffect`, effect)
194+
}
195+
201196
// UseEffect registers an effect function that is called after rendering.
202197
// The effect is re-run whenever any of the dependencies change.
203-
// If no dependencies are provided, the effect is only run once after the initial render.
198+
// If no dependency are provided, the effect is only run once after the initial render.
204199
// See: https://react.dev/reference/react/useEffect
205200
func UseEffect(effect func(), deps ...any) {
206201
react().Call(`useEffect`, effect, deps)
207202
}
203+
204+
// UseEffectNoDepsWithCleanup registers an effect function that is called after every render.
205+
// The effect function returns a cleanup function that is called before the effect
206+
// is re-run or when the component is unmounted.
207+
// See: https://react.dev/reference/react/useEffect
208+
func UseEffectNoDepsWithCleanup(effect func() func()) {
209+
react().Call(`useEffect`, effect)
210+
}
211+
212+
// UseEffectWithCleanup registers an effect function that is called after rendering.
213+
// The effect is re-run whenever any of the dependencies change.
214+
// If no dependency are provided, the effect is only run once after the initial render.
215+
// The effect function returns a cleanup function that is called before the effect
216+
// is re-run or when the component is unmounted.
217+
// See: https://react.dev/reference/react/useEffect
218+
func UseEffectWithCleanup(effect func() func(), deps ...any) {
219+
react().Call(`useEffect`, effect, deps)
220+
}

playground/internal/react/codeBox.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ type codeBox struct {
1919

2020
var _ Component = (*codeBox)(nil)
2121

22-
func NewCodeBox() common.CodeBox {
23-
return &codeBox{
24-
code: NewState(``),
25-
}
26-
}
22+
func NewCodeBox() common.CodeBox { return &codeBox{} }
2723

2824
func (c *codeBox) onInput(e *js.Object) {
2925
invokeCallback(c.onInputCallback)
@@ -55,13 +51,13 @@ func (c *codeBox) GetCode() string { return c.code.Get() }
5551
func (c *codeBox) SetCode(code string) { c.code.Set(code) }
5652

5753
func (c *codeBox) Render(props Props) *Element {
58-
c.code.Use()
54+
c.code = UseState(``)
5955
c.codeAreaRef = UseRef()
6056

61-
return Div(Props{
57+
return Div(props.Add(Props{
6258
`className`: `box`,
6359
`id`: `content`,
64-
}, Div(Props{
60+
}), Div(Props{
6561
`className`: `box yellow`,
6662
`id`: `input`,
6763
}, CreateElement(`textarea`, Props{

playground/internal/react/outputBox.go

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,13 @@ const outputBoxId = `output`
1212

1313
type line map[string]string
1414

15-
func newLine(typ, content string) line {
16-
return line{`type`: typ, `content`: content}
17-
}
15+
func newLine(typ, content string) line { return line{`type`: typ, `content`: content} }
1816

19-
func newOutLine(content string) line {
20-
return newLine(`out`, content)
21-
}
17+
func newOutLine(content string) line { return newLine(`out`, content) }
18+
func newErrLine(content string) line { return newLine(`err`, content) }
2219

23-
func newErrLine(content string) line {
24-
return newLine(`err`, content)
25-
}
26-
27-
func (l line) isOut() bool {
28-
return l[`type`] == `out`
29-
}
30-
31-
func (l line) appendContent(content string) {
32-
l[`content`] += content
33-
}
20+
func (l line) isOut() bool { return l[`type`] == `out` }
21+
func (l line) appendContent(content string) { l[`content`] += content }
3422

3523
// outputBox implements playground.OutputBox using AngularJS.
3624
//
@@ -43,11 +31,7 @@ type outputBox struct {
4331

4432
var _ Component = (*outputBox)(nil)
4533

46-
func NewOutputBox() common.OutputBox {
47-
return &outputBox{
48-
outputState: NewState[any](nil),
49-
}
50-
}
34+
func NewOutputBox() common.OutputBox { return &outputBox{} }
5135

5236
// scrollToBottom scrolls the output box to the bottom
5337
// after the current digest cycle completes so that the new output is visible.
@@ -101,7 +85,7 @@ func (ob *outputBox) AddOutput(out string) {
10185
}
10286

10387
func (ob *outputBox) Render(props Props) *Element {
104-
ob.outputState.Use()
88+
ob.outputState = UseState[any](nil)
10589
ob.boxRef = UseRef()
10690

10791
output := ob.output
@@ -111,8 +95,8 @@ func (ob *outputBox) Render(props Props) *Element {
11195
`class`: line[`type`],
11296
}, line[`content`], `&nbsp;`)
11397
}
114-
return Div(Props{
98+
return Div(props.Add(Props{
11599
`className`: `box`,
116100
`id`: outputBoxId,
117-
}, messages...)
101+
}), messages...)
118102
}

playground/playground.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,30 @@ import (
88
)
99

1010
func main() {
11+
root := react.CreateRoot(`root`)
12+
root.Render(react.StrictMode(pg{}))
13+
}
14+
15+
type pg struct{}
16+
17+
func (pg) Render(props react.Props) *react.Element {
18+
1119
// Setup the React components and render the app.
1220
banner := react.NewBanner()
1321
codeBox := react.NewCodeBox()
1422
output := react.NewOutputBox()
15-
root := react.CreateRoot(`root`)
16-
root.Render(react.StrictMode(
17-
banner, codeBox, output,
18-
))
1923

2024
// Setup the playground logic and networking.
2125
snippetsStore := snippets.NewStore()
2226
fetcher := runner.NewFetcher()
2327
runner := runner.New(output, fetcher)
2428
playground.New(banner, codeBox, output, snippetsStore, runner)
29+
30+
return react.Div(react.Props{
31+
`className`: `playground`,
32+
},
33+
banner,
34+
codeBox,
35+
output,
36+
)
2537
}

0 commit comments

Comments
 (0)