Skip to content

Commit a071e0a

Browse files
Making react work
1 parent 8205c36 commit a071e0a

5 files changed

Lines changed: 142 additions & 180 deletions

File tree

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

33
import (
4-
"errors"
5-
"reflect"
6-
74
"github.com/gopherjs/gopherjs/js"
85
)
96

107
type (
11-
// Component is a React component that can be rendered.
12-
Component struct{ *js.Object }
13-
14-
// State represents the state of a React component.
15-
// It can be a map, a struct, or a pointer to a struct.
16-
State any
17-
18-
// Renderable is an interface for types that can render React nodes.
19-
// This is used to let the Component call the Render method
20-
// in the "extending" struct.
21-
Renderable interface{ Render() Node }
8+
Component struct{ *js.Object }
9+
State map[string]any
10+
RenderFunc func(*Component) Node
2211
)
2312

24-
func NewComponent(props Props, r Renderable, initialState State) *Component {
25-
c := &Component{Object: react().Get(`Component`).New(props)}
26-
c.Set(`render`, func() Node { return r.Render() })
27-
c.Set(`state`, stateToMap(initialState))
28-
return c
29-
}
30-
31-
func (c *Component) Render() Node {
32-
return c.Call(`render`)
13+
func NewComponent(props Props, init State, render RenderFunc) *Element {
14+
reactComp := react().Get("Component")
15+
ctor := js.MakeFunc(func(this *js.Object, args []*js.Object) any {
16+
reactComp.Call(`call`, props)
17+
this.Set(`state`, init)
18+
return nil
19+
})
20+
rf := js.MakeFunc(func(this *js.Object, args []*js.Object) any {
21+
return render(&Component{Object: this})
22+
})
23+
ctor.Set(`prototype`, reactComp.Get(`prototype`))
24+
ctor.Get(`prototype`).Set(`constructor`, ctor)
25+
ctor.Get("prototype").Set("render", rf)
26+
return &Element{Object: react().Call(`createElement`, ctor)}
3327
}
3428

35-
func (c *Component) GetState(name string) *js.Object {
36-
return c.Get(`state`).Get(`state`)
29+
func (c *Component) GetState(key string) *js.Object {
30+
return c.Get("state").Get(key)
3731
}
3832

39-
func (c *Component) SetState(state State) {
40-
c.Call(`setState`, stateToMap(state))
33+
func (c *Component) SetState(newState State) {
34+
c.Call("setState", newState)
4135
}
4236

43-
func stateToMap(v State) map[string]any {
44-
if v == nil {
45-
return nil
46-
}
47-
r := reflect.ValueOf(v)
48-
if r.Kind() == reflect.Ptr {
49-
r = r.Elem()
50-
}
51-
m := make(map[string]any)
52-
switch r.Kind() {
53-
case reflect.Map:
54-
for _, key := range r.MapKeys() {
55-
m[key.String()] = r.MapIndex(key).Interface()
56-
}
57-
case reflect.Struct:
58-
t := r.Type()
59-
for i := 0; i < r.NumField(); i++ {
60-
name := t.Field(i).Name
61-
if tag := t.Field(i).Tag.Get(`js`); tag != `` {
62-
name = tag
63-
}
64-
m[name] = r.Field(i).Interface()
65-
}
66-
default:
67-
panic(errors.New(`states must be a map, a struct, or a pointer to a struct`))
68-
}
69-
return m
37+
//============================================
38+
39+
func NewCounter(initName string, initAge int) *Element {
40+
return NewComponent(Props{}, map[string]any{
41+
`name`: initName,
42+
`age`: initAge,
43+
}, func(c *Component) Node {
44+
return Fragment(
45+
CreateElement(`input`, Props{
46+
`value`: c.GetState(`name`).String(),
47+
`onChange`: func(e *js.Object) {
48+
c.SetState(State{`name`: e.Get(`target`).Get(`value`).String()})
49+
},
50+
}),
51+
CreateElement(`input`, Props{
52+
`type`: `button`,
53+
`value`: `Increment age`,
54+
`onClick`: func() {
55+
c.SetState(State{`age`: c.GetState(`age`).Int() + 1})
56+
},
57+
}),
58+
CreateElement(`p`, nil,
59+
`Hello, `, c.GetState(`name`).String(), `. You are `, c.GetState(`age`).Int(), `.`),
60+
)
61+
})
7062
}
Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package react
22

3-
import "github.com/gopherjs/gopherjs/js"
4-
53
func Fragment(children ...Node) *Element {
64
args := []any{react().Get(`Fragment`), nil}
75
for _, c := range children {
@@ -24,73 +22,3 @@ func Button(value string, props Props, onClick func()) *Element {
2422
props[`onClick`] = onClick
2523
return CreateElement(`input`, props)
2624
}
27-
28-
// =============================================================
29-
30-
type Counter struct {
31-
*Component
32-
}
33-
34-
func (c *Counter) handleNameChange(e *js.Object) {
35-
c.SetState(map[string]any{
36-
`name`: e.Get(`target`).Get(`value`).String(),
37-
})
38-
}
39-
40-
func (c *Counter) handleAgeChange() {
41-
c.SetState(map[string]any{
42-
`age`: c.GetState(`age`).Int() + 1,
43-
})
44-
}
45-
46-
func (c *Counter) Render() Node {
47-
return Fragment(
48-
CreateElement(`input`, Props{
49-
`value`: c.GetState(`name`).String(),
50-
`onChange`: c.handleNameChange,
51-
}),
52-
CreateElement(`input`, Props{
53-
`type`: `button`,
54-
`value`: `Increment age`,
55-
`onClick`: c.handleAgeChange,
56-
}),
57-
CreateElement(`p`, nil,
58-
`Hello, `, c.GetState(`name`).String(), `. You are `, c.GetState(`age`).Int(), `.`),
59-
)
60-
}
61-
62-
func NewCounter() *Counter {
63-
c := &Counter{}
64-
c.Component = NewComponent(Props{}, c, map[string]any{
65-
`name`: `Taylor`,
66-
`age`: 42,
67-
})
68-
return c
69-
}
70-
71-
// =============================================================
72-
73-
// Returns a JS class (constructor) for a React component
74-
func NewReactComponentClass(renderFunc func() Node) *js.Object {
75-
reactComp := react().Get("Component")
76-
// Create a constructor function
77-
ctor := js.MakeFunc(func(this *js.Object, args []*js.Object) any {
78-
// Call React.Component constructor
79-
reactComp.Call(`call`, this)
80-
return nil
81-
})
82-
proto := ctor.Get("prototype")
83-
// Set up inheritance: ctor.prototype = Object.create(React.Component.prototype)
84-
proto.Set("__proto__", reactComp.Get("prototype"))
85-
// Set the render method on the prototype
86-
proto.Set("render", renderFunc)
87-
return ctor
88-
}
89-
90-
func NewMyComponent() *Element {
91-
MyComponentClass := NewReactComponentClass(func() Node {
92-
return Div(nil, `Hello from GopherJS (2)!`)
93-
})
94-
args := []any{MyComponentClass, nil}
95-
return &Element{Object: react().Call(`createElement`, args...)}
96-
}

playground/internal/react/react.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ func checkNode(n Node) Node {
5353
switch c := n.(type) {
5454
case *Element, string, int, float64:
5555
return c
56-
case interface{ Interface() any }:
57-
return &Element{Object: react().Call(`createElement`, c.Interface())}
5856
default:
5957
panic(fmt.Errorf(`unsupported child type %T`, c))
6058
}

playground/playground.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ func main() {
2323

2424
//root.Render(react.NewCounter())
2525

26-
root.Render(react.NewMyComponent())
26+
root.Render(react.NewCounter(`grant`, 48))
2727
}

0 commit comments

Comments
 (0)