React and GraphQL, plus GoLang
This is some useful inisights I've gained, that are worth noting. The code examples are from a gist clone that I'm playing around with, it's easier to have a "known" problem and target to experiment with software than trying to do both at the same time.
React
What's clear is that with appropriate use of PropTypes, you really have a well structured "strict" environment for building components. It's nice to see that when you pass the wrong parameter into a directive that it fails fast. On my agenda is to move my ES6 codebase over to TypeScript to see what having and even more strictly typed environment does.
React is good that it's helping put the motivation under JS and the practices of front end development to have stronger types which reduce errors in the long run. I see this in my Go code frequently, where a fumble finger would have created a new variable or assigned it to the wrong thing.
What's interesting is when React decides to change the life cycle of a component and the additional work that you need to do when that component is the main view. This was a recent example of that.
1 constructor(props) {
2 super(props)
3
4 const { user, guid } = props.params
5
6 this.state = this._build_state(guid, user)
7 }
8
9 componentWillMount() {
10 this._fetch(this.state.guid, this.state.user)
11 }
12
13 componentWillReceiveProps(nextProps) {
14 const { user, guid } = nextProps.params
15
16 this.setState(this._build_state(user, guid))
17 this._fetch(user, guid)
18 }
GraphQL
Provides a strongly typed well defined API schema that once you wrap your head around the queries, mutations slightly weird libraries and packages that hold it all together it's pretty darn cool.
For instance it's easy to define the following type structure:
1 type User {
2 id String!
3 username String!
4 email String!
5 first_name String
6 last_name String
7 image_url String
8
9 gists(id: String) [Gist]
10 }
11
12 type Gist {
13 id String!
14 title String
15 version String!
16 files [GistFile]
17 }
18
19 type GistFile {
20 id String!
21 version String!
22 name String
23 body String
24 }
What's really nice is the API now is defined to say what's required fields and I can also ask for a single Gist (which returns a list of 0 or 1 elements). In one API call I can get the user information, their 5 most recent gist (ok, that API isn't there yet) and the files that it represents.
Redux
I've finally gotten my head around how to use @connect((state) => { ... }
correctly. It's amazingly powerful, to have components react to changes. For instance when you log into the application the "App" knows to go refetch the use information to surface (e.g. that drop menu with your avatar attached).
1@connect((state) => {
2 return {
3 dispatch: state.dispatch,
4 isAuthenticated: state.auth.isAuthenticated,
5 };
6})
7class App extends Component {
8 static propTypes = {
9 dispatch: PropTypes.func.isRequired,
10 isAuthenticated: PropTypes.bool.isRequired,
11 };
12
13 componentWillMount() {
14 const { dispatch, isAuthenticated } = this.props;
15
16 if (isAuthenticated) dispatch(getUserData(client));
17 }
18
19 componentWillReceiveProps(nextProps) {
20 const { dispatch, isAuthenticated } = this.props;
21
22 if (nextProps.isAuthenticated != isAuthenticated) {
23 if (nextProps.isAuthenticated) {
24 dispatch(getUserData(client));
25 } else {
26 dispatch(clearUserData());
27 }
28 }
29 }
30
31 //....
32}
Golang + GraphQL
As you may already know the key idea of GraphQL is that you can dispatch a single query and get back a fairly extensive result, rather than making 37 different REST calls to fetch the same data, or maybe have lots of parameters to an API to include and exclude specific things.
If you read back through some of my blog posts, I spent a great deal of time polishing rocks working on an API design. Well as soon as I dropped in the GraphQL library which has it's own world view, those got tossed out the window. I'm back to having "200" success and "400" for something went wrong, including the object wasn't found.
However, the really great part about the graphql-go library is the way that it lets you defer the fetching of data. So for instance that type system you see above, the DB call to fetch the file isn't made unless you ask for it and it's easy to have a single structured API to handle it.
1 "files": &graphql.Field{
2 Type: graphql.NewList(gistFileType),
3 Resolve: graphqlResolve(func(ctx graphqlContext) (interface{}, error) {
4 gist := ctx.params.Source.(*models.Gist)
5
6 if err := gist.LoadFiles(); err != nil {
7 ctx.logger.With(
8 zap.String("id", gist.Guid),
9 zap.Error(err),
10 ).Error("unable to get gist files")
11 return nil, err
12 }
13
14 return gist.Files, nil
15 }),
16 },
That's almost magic, it will only take about 10 minutes to add "offset" and "count" arguments for files. The only "ugly" side of the graphql-go library is on the Input side -- side note: it took me way to long to understand that there are Input types that are distinct from Output types. For simple arguments everything is "easy", the challenge is that when you post back up a Gist + Files, there is no way to have the library marshal it into an object structure for you (at least that I've found). So you end up with some warty code like:
1 gist.Files = make([]*models.GistFile, 0)
2
3 for _, file := range files {
4 data, ok := file.(map[string]interface{})
5 if !ok {
6 // TODO: Bad type, do something
7 }
8
9 f := gist.NewFile()
10
11 for k, v := range data {
12 switch k {
13 //case "id":
14 // f.Guid = v.(string)
15 case "name":
16 f.Name = strings.TrimSpace(v.(string))
17 case "body":
18 f.Body = v.(string)
19 }
20 }
21
22 // ... code removed ...
23 }
Parting thought
It takes time to really get comfortable with something, it's really nice to have the time to dig into this and really get a complete understanding. I'm really excited that it's so "easy" to create rich single page apps.