Skip to content

Commit 188b50c

Browse files
trying to debug react
1 parent 2a0d3f0 commit 188b50c

File tree

11 files changed

+23166
-15597
lines changed

11 files changed

+23166
-15597
lines changed

go.work

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
go 1.19
22

33
use (
4-
./playground
5-
./playground/internal/cmd/precompile
4+
./playground
5+
./playground/internal/cmd/precompile
66
)

go.work.sum

Lines changed: 595 additions & 0 deletions
Large diffs are not rendered by default.

playground/internal/cmd/precompile/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/grantnelson-wf/gopherjs.github.io/playground/internal/cmd/prec
33
go 1.19
44

55
require (
6-
github.com/gopherjs/gopherjs v1.19.0-beta2
6+
github.com/gopherjs/gopherjs v1.19.0-beta2.0.20251008200541-113c92b42b18
77
github.com/sirupsen/logrus v1.8.1
88
)
99

@@ -13,6 +13,7 @@ require (
1313
github.com/msvitok77/goembed v0.3.5 // indirect
1414
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 // indirect
1515
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c // indirect
16+
github.com/stretchr/testify v1.11.1 // indirect
1617
golang.org/x/sys v0.10.0 // indirect
1718
golang.org/x/tools v0.16.0 // indirect
1819
)

playground/internal/cmd/precompile/go.sum

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

playground/internal/playground/playground.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,24 @@ func (p *Playground) onCodeKeyDown(keyCode int) bool {
114114
toInsert += p.currentIndent()
115115
}
116116

117-
p.code.SetCode(code[:start] + toInsert + code[end:])
117+
var head, tail string
118+
if start > 0 {
119+
head = code[:start]
120+
}
121+
if end < len(code) {
122+
tail = code[end:]
123+
}
124+
125+
print(fmt.Sprintf(`>>Start: [%d] %q`, start, head)) // TODO(grantnelson-wf): REMOVE
126+
print(fmt.Sprintf(`>>Insert: [%d] %q`, len(toInsert), toInsert)) // TODO(grantnelson-wf): REMOVE
127+
print(fmt.Sprintf(`>>End: [%d] %q`, end, tail)) // TODO(grantnelson-wf): REMOVE
128+
129+
code = head + toInsert + tail
118130
newCaret := start + len(toInsert)
131+
132+
print(fmt.Sprintf(`>>After: [%d] %q`, newCaret, code)) // TODO(grantnelson-wf): REMOVE
133+
134+
p.code.SetCode(code)
119135
p.code.SetSelection(newCaret, newCaret)
120136
return true
121137
}

playground/internal/react/banner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type banner struct {
1919
shareUrl *State[string]
2020
shareUrlVisible *State[bool]
2121
version *State[string]
22-
shareUrlRef *js.Object
22+
shareUrlRef *Ref
2323

2424
location *dom.Location
2525
}
@@ -114,6 +114,7 @@ func (b *banner) Render(props Props) *Element {
114114
CreateElement(`label`, Props{
115115
`title`: `Rewrite imports on Format`,
116116
}, CreateElement(`input`, Props{
117+
`id`: `rewrite-imports`,
117118
`type`: `checkbox`,
118119
`checked`: b.importChecked.Get(),
119120
`onChange`: b.onImportCheckedChange,

playground/internal/react/bindings.go

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package react
22

33
import (
4+
"fmt"
5+
46
"github.com/gopherjs/gopherjs/js"
57
)
68

@@ -28,16 +30,24 @@ type (
2830

2931
// State is a value managed by React's useState hook.
3032
// If the component function hasn't been called yet,
31-
// Get returns zero and Set has no effect, otherwise
32-
// it will return the current value and update it respectively.
33-
// NOTE: `State[int]` should be `State[float64]` because of
34-
// how the underlying React hook works.
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.
3535
// See: https://react.dev/reference/react/useState
36+
//
37+
// NOTE: Some types like `State[int]` must instead be `State[float64]`,
38+
// and `State[[]*foo]` must instead be `State[[]any]`, because of
39+
// how the underlying React hook works.
3640
State[T any] struct {
3741
initial T
3842
getter, setter *js.Object
3943
}
4044

45+
// Ref is a React ref created with UseRef().
46+
// It is a mutable object with a `current` property that can hold any value.
47+
// Modifying the `current` property does not trigger re-renders.
48+
// See: https://react.dev/reference/react/useRef
49+
Ref struct{ holder *js.Object }
50+
4151
// ComponentFunc is a function that defines a React component.
4252
// It takes props as input and returns a React element.
4353
// See: https://react.dev/learn
@@ -47,6 +57,10 @@ type (
4757
// It is an interface that exposes a Render methods with the same signature
4858
// as a ComponentFunc so that it can be used simular to a React component.
4959
Component interface{ Render(props Props) *Element }
60+
61+
// Root is a React root created with CreateRoot().
62+
// It is used to render React nodes into the DOM.
63+
// See: https://react.dev/reference/react-dom/client/createRoot
5064
)
5165

5266
func reactDom() *js.Object { return js.Global.Get(`ReactDOM`) }
@@ -114,40 +128,80 @@ func Button(value string, props Props, onClick func(e *js.Object)) *Element {
114128
return CreateElement(`input`, props)
115129
}
116130

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
117135
func NewState[T any](initial T) *State[T] {
136+
fmt.Printf("NewState called [%T]\n", initial) // TODO(grantnelson-wf): REMOVE
118137
return &State[T]{initial: initial}
119138
}
120139

140+
// Use initializes the state for the current component render.
141+
// 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.
144+
// See: https://react.dev/reference/react/useState
121145
func (s *State[T]) Use() {
146+
fmt.Printf("Use called [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
122147
r := react().Call(`useState`, s.initial)
123148
s.getter = r.Index(0)
124149
s.setter = r.Index(1)
125150
}
126151

127-
func (s *State[T]) Get() (v T) {
152+
func (s *State[T]) Get() T {
128153
if s != nil {
129154
if s.getter != nil {
155+
fmt.Printf("Get called via getter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
130156
return s.getter.Interface().(T)
131157
}
158+
fmt.Printf("Get called without getter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
132159
return s.initial
133160
}
134-
return
161+
var zero T
162+
fmt.Printf("Get called on nil [%T]\n", zero) // TODO(grantnelson-wf): REMOVE
163+
return zero
135164
}
136165

137166
func (s *State[T]) Set(v T) {
138167
if s != nil {
139168
if s.setter != nil {
169+
fmt.Printf("Set called with setter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
140170
s.setter.Invoke(v)
141171
return
142172
}
173+
fmt.Printf("Set called without setter [%T]\n", s.initial) // TODO(grantnelson-wf): REMOVE
143174
s.initial = v
144175
}
145176
}
146177

147-
func UseRef() *js.Object {
148-
return react().Call(`useRef`, nil)
178+
// UseRef creates a mutable ref object that persists for the lifetime of the component.
179+
// The ref object has a `current` property that can hold any value.
180+
// See: https://react.dev/reference/react/useRef
181+
func UseRef() *Ref {
182+
return &Ref{holder: react().Call(`useRef`, nil)}
183+
}
184+
185+
func (r *Ref) Current() *js.Object {
186+
return r.holder.Get(`current`)
187+
}
188+
189+
func (r *Ref) Get(key string) *js.Object {
190+
return r.Current().Get(key)
191+
}
192+
193+
func (r *Ref) Set(key string, value any) {
194+
r.Current().Set(key, value)
195+
}
196+
197+
func (r *Ref) Call(name string, args ...any) {
198+
r.Current().Call(name, args...)
149199
}
150200

201+
// UseEffect registers an effect function that is called after rendering.
202+
// 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.
204+
// See: https://react.dev/reference/react/useEffect
151205
func UseEffect(effect func(), deps ...any) {
152206
react().Call(`useEffect`, effect, deps)
153207
}

playground/internal/react/codeBox.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type codeBox struct {
1414
onKeydownCallback func(keyCode int) bool
1515

1616
code *State[string]
17-
codeAreaRef *js.Object
17+
codeAreaRef *Ref
1818
}
1919

2020
var _ Component = (*codeBox)(nil)
@@ -31,7 +31,7 @@ func (c *codeBox) onInput(e *js.Object) {
3131

3232
func (c *codeBox) onKeyDown(e *js.Object) {
3333
if c.onKeydownCallback != nil && c.onKeydownCallback(e.Get(`keyCode`).Int()) {
34-
e.Call(`PreventDefault`)
34+
e.Call(`preventDefault`)
3535
}
3636
}
3737

@@ -41,6 +41,8 @@ func (c *codeBox) SetKeyDownCallback(callback func(keyCode int) bool) { c.onKeyd
4141
func (c *codeBox) GetSelection() (int, int) {
4242
start := c.codeAreaRef.Get(`selectionStart`).Int()
4343
end := c.codeAreaRef.Get(`selectionEnd`).Int()
44+
print(c.codeAreaRef) // TODO(grantnelson-wf): REMOVE
45+
print(`::`, start, `, `, end) // TODO(grantnelson-wf): REMOVE
4446
return start, end
4547
}
4648

playground/internal/react/outputBox.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"strings"
66
"time"
77

8-
"github.com/gopherjs/gopherjs/js"
9-
108
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
119
)
1210

@@ -40,7 +38,7 @@ func (l line) appendContent(content string) {
4038
type outputBox struct {
4139
output []line
4240
outputState *State[any]
43-
boxRef *js.Object
41+
boxRef *Ref
4442
}
4543

4644
var _ Component = (*outputBox)(nil)
@@ -56,7 +54,7 @@ func NewOutputBox() common.OutputBox {
5654
func (ob *outputBox) scrollToBottom() {
5755
// TODO(grantnelson-wf): This should probably use React's UseEffect.
5856
time.AfterFunc(0, func() {
59-
ob.boxRef.Set(`scrollTop`, ob.boxRef.Get(`scrollHeight`))
57+
ob.boxRef.Current().Set(`scrollTop`, ob.boxRef.Current().Get(`scrollHeight`))
6058
})
6159
}
6260

playground/internal/runner/runner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"go/token"
99
"go/types"
1010
"runtime"
11+
_ "unsafe"
1112

1213
"github.com/gopherjs/gopherjs/compiler"
1314
"github.com/gopherjs/gopherjs/compiler/sources"
@@ -155,7 +156,7 @@ func (r *runner) prepareAndCompilePackages(allSources []*sources.Sources) ([]*co
155156
func (r *runner) write(allPkgs []*compiler.Archive) string {
156157
jsCode := bytes.NewBuffer(nil)
157158
jsCode.WriteString("try{\n")
158-
compiler.WriteProgramCode(allPkgs, &compiler.SourceMapFilter{Writer: jsCode}, runtime.Version())
159+
compiler.WriteProgramCode(allPkgs, compiler.DefaultFilter(jsCode), runtime.Version())
159160
jsCode.WriteString("} catch (err) {\n")
160161
jsCode.WriteString("\tgoPanicHandler(err.message);\n")
161162
jsCode.WriteString("}\n")

0 commit comments

Comments
 (0)