Skip to content

Commit 4ed5dd1

Browse files
Getting react working
1 parent 83d7af4 commit 4ed5dd1

5 files changed

Lines changed: 18579 additions & 332890 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package react
2+
3+
import (
4+
"errors"
5+
"reflect"
6+
7+
"github.com/gopherjs/gopherjs/js"
8+
)
9+
10+
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 }
22+
)
23+
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`)
33+
}
34+
35+
func (c *Component) GetState(name string) *js.Object {
36+
return c.Get(`state`).Get(`state`)
37+
}
38+
39+
func (c *Component) SetState(state State) {
40+
c.Call(`setState`, stateToMap(state))
41+
}
42+
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
70+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package react
2+
3+
import "github.com/gopherjs/gopherjs/js"
4+
5+
func Fragment(children ...Node) *Element {
6+
args := []any{react().Get(`Fragment`), nil}
7+
for _, c := range children {
8+
args = append(args, checkNode(c))
9+
}
10+
return &Element{Object: react().Call(`createElement`, args...)}
11+
}
12+
13+
func Div(props Props, children ...Node) *Element {
14+
return CreateElement(`div`, props, children...)
15+
}
16+
17+
func Span(props Props, children ...Node) *Element {
18+
return CreateElement(`span`, props, children...)
19+
}
20+
21+
func Button(value string, props Props, onClick func()) *Element {
22+
props[`value`] = value
23+
props[`type`] = `button`
24+
props[`onClick`] = onClick
25+
return CreateElement(`input`, props)
26+
}
27+
28+
type Counter struct {
29+
*Component
30+
}
31+
32+
func (c *Counter) handleNameChange(e *js.Object) {
33+
c.SetState(map[string]any{
34+
`name`: e.Get(`target`).Get(`value`).String(),
35+
})
36+
}
37+
38+
func (c *Counter) handleAgeChange() {
39+
c.SetState(map[string]any{
40+
`age`: c.GetState(`age`).Int() + 1,
41+
})
42+
}
43+
44+
func (c *Counter) Render() Node {
45+
return Fragment(
46+
CreateElement(`input`, Props{
47+
`value`: c.GetState(`name`).String(),
48+
`onChange`: c.handleNameChange,
49+
}),
50+
CreateElement(`input`, Props{
51+
`type`: `button`,
52+
`value`: `Increment age`,
53+
`onClick`: c.handleAgeChange,
54+
}),
55+
CreateElement(`p`, nil,
56+
`Hello, `, c.GetState(`name`).String(), `. You are `, c.GetState(`age`).Int(), `.`),
57+
)
58+
}
59+
60+
func NewCounter() *Counter {
61+
c := &Counter{}
62+
c.Component = NewComponent(Props{}, c, map[string]any{
63+
`name`: `Taylor`,
64+
`age`: 42,
65+
})
66+
return c
67+
}
Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package react
22

3-
import "github.com/gopherjs/gopherjs/js"
3+
import (
4+
"fmt"
5+
6+
"github.com/gopherjs/gopherjs/js"
7+
)
48

59
type (
610
// Node is a React node that can be displayed.
711
// This will usually be a react element constructed with CreateElement(),
8-
// a ComponentFunc, a string, a number, null, or undefined.
12+
// a string, a number, null, or undefined.
13+
// Node may be a slice of Nodes as well.
914
Node any
1015

1116
// Root is a React root created with CreateRoot().
@@ -33,10 +38,33 @@ func CreateRoot(id string) *Root {
3338
}
3439

3540
func (r *Root) Render(n Node) {
36-
r.Call(`render`, n)
41+
r.Call(`render`, checkNode(n))
3742
}
3843

39-
func CreateElement(typ string, props Props, children ...any) *Element {
40-
args := append([]any{typ, props}, children...)
44+
func CreateElement(typ string, props Props, children ...Node) *Element {
45+
args := []any{typ, props}
46+
for _, c := range children {
47+
args = append(args, checkNode(c))
48+
}
4149
return &Element{Object: react().Call(`createElement`, args...)}
4250
}
51+
52+
func checkNode(n Node) Node {
53+
switch c := n.(type) {
54+
case Renderable:
55+
return &Element{Object: react().Call(`createElement`, c.Render)}
56+
case *Element, string, int, float64:
57+
return c
58+
default:
59+
panic(fmt.Errorf(`unsupported child type %T`, c))
60+
}
61+
}
62+
63+
func SimpleElement() *Element {
64+
c := react().Get(`Component`).New()
65+
c.Set(`render`, func() Node {
66+
return CreateElement(`div`, nil, `Hello World!`)
67+
})
68+
69+
return &Element{Object: react().Call(`createElement`, c.Get(`render`))}
70+
}

playground/playground.go

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,25 @@
11
package main
22

33
import (
4-
_ "embed"
5-
6-
"github.com/neelance/go-angularjs"
7-
8-
"github.com/gopherjs/gopherjs.github.io/playground/internal/angular"
9-
"github.com/gopherjs/gopherjs.github.io/playground/internal/playground"
104
"github.com/gopherjs/gopherjs.github.io/playground/internal/react"
11-
"github.com/gopherjs/gopherjs.github.io/playground/internal/runner"
12-
"github.com/gopherjs/gopherjs.github.io/playground/internal/snippets"
135
)
146

157
func main() {
16-
if false {
17-
app := angularjs.NewModule("playground", nil, nil)
18-
app.NewController("PlaygroundCtrl", func(scope *angularjs.Scope) {
19-
banner := angular.NewBanner(scope)
20-
codeBox := angular.NewCodeBox(scope)
21-
output := angular.NewOutputBox(scope)
22-
snippetsStore := snippets.NewStore()
23-
fetcher := runner.NewFetcher()
24-
runner := runner.New(output, fetcher)
25-
playground.New(banner, codeBox, output, snippetsStore, runner)
26-
})
27-
}
8+
//if false {
9+
// app := angularjs.NewModule("playground", nil, nil)
10+
// app.NewController("PlaygroundCtrl", func(scope *angularjs.Scope) {
11+
// banner := angular.NewBanner(scope)
12+
// codeBox := angular.NewCodeBox(scope)
13+
// output := angular.NewOutputBox(scope)
14+
// snippetsStore := snippets.NewStore()
15+
// fetcher := runner.NewFetcher()
16+
// runner := runner.New(output, fetcher)
17+
// playground.New(banner, codeBox, output, snippetsStore, runner)
18+
// })
19+
//}
2820

2921
root := react.CreateRoot(`root`)
30-
root.Render(react.CreateElement(`div`, nil, `Hello World!`))
22+
//root.Render(react.CreateElement(`div`, nil, `Hello World!`))
3123

24+
root.Render(react.NewCounter())
3225
}

0 commit comments

Comments
 (0)