From 1ff7350a636fe869d970fd8bbabf75426ac1a5aa Mon Sep 17 00:00:00 2001
From: William Billingsley <wbilling@une.edu.au>
Date: Tue, 7 Aug 2018 18:34:58 +1000
Subject: [PATCH] Added React instructions

---
 README.md | 203 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 180 insertions(+), 23 deletions(-)

diff --git a/README.md b/README.md
index b085477..e370eb1 100644
--- a/README.md
+++ b/README.md
@@ -117,7 +117,7 @@ export interface GameConfig {
 
 The next task I would suggest is just creating this object and rendering it to a little UI using your chosen framework. That'll let you fiddle around with the layout.
 
-Then, wire up the functionality. Note that you'll probably need to change `life`. At the moment, it's a constant in that file. You will probably want it to be a variable that can be set by calling a function.
+Then, wire up the functionality. Note that you'll probably need to change `life`. At the moment, it's a constant in the render file. You will probably want it to be a variable that can be set by calling a function.
 
 ## Solving this with Vue
 
@@ -301,7 +301,7 @@ Reload and check that logs to the browser console when you hit the button (and t
 
 We need to let our component actually configure our `Life` instance.
 
-First, in `life.ts`, let's turn `life` into a variable, and also add a create function that accepts a `GameConfig` and a function for getting the value of life.
+First, in `render.ts`, let's turn `life` into a variable, and also add a create function that accepts a `GameConfig` and a function for getting the value of life.
 
 ```ts
 import { GameConfig } from "./gameConfig"
@@ -469,60 +469,217 @@ import * as React from "react"
 import * as ReactDOM from "react-dom"
 ```
 
-Then let's define our GameConfig type:
+Then let's define two types -- one for the config's definable properties, and another that has some additional state:
 
 ```tsx
-export interface GameConfig {
+export interface ConfigProps {
     w:number
     h:number
-    started:boolean
+}
+```
+
+```tsx
+export interface ConfigState extends ConfigProps {
     created:boolean
     period:number
+    intervalId?: number
 }
 ```
 
+intervalId is optional -- hence the `?`
+
 And then let's define a React component:
 
 ```tsx
-export class ConfigView extends React.Component<GameConfig, {}> {
+export class ConfigView extends React.Component<ConfigProps, ConfigState> {
     render() {
         return <div>This will render some game config</div>
     }
+
+    constructor(props:ConfigProps) {
+        super(props)
+        this.state = {
+            ...props,
+            created: false,
+            period: 500
+        }
+    }
+
 }
 ```
 
-Notice that in the angle brackets, we've said this component's properties are defined by the GameConfig interface -- so, it has properties for `w`, `h`, etc.
+Notice that in the angle brackets, we've said this component's properties are defined by the ConfigProps interface -- so, it has properties for `w` and`h`.
+And we've said its state is defined by the `ConfigState` interface.
 
-Let's go back to `index.tsx` and use it. 
+In the constructor, we've also taken the component's properties, and augmented them with the missing compulsory variables. `...props` is the "spread" operator that sets all the name-value pairs from
+`props` on the new object we're creating.
 
-First, we need to import `GameConfig` and `ConfigView`:
+Let's go back to `index.tsx` and use our component.
 
-```tsx
-import { GameConfig, ConfigView } from "./gameConfig"
-```
-
-Then we need to create some config:
+First, we need to import `ConfigView`:
 
 ```tsx
-let config:GameConfig = {
-    w: 40,
-    h: 20,
-    created: false,
-    started: false,
-    period: 500
-}
+import { ConfigView } from "./gameConfig"
 ```
 
 and then let's alter our React call to use it:
 
 ```tsx
 ReactDOM.render(
-    <ConfigView {...config}></ConfigView>,
+    <ConfigView w={40} h={20}></ConfigView>,
     document.querySelector("#app")
 );
 ```
 
-We've used the *spread* operator `{...config}` here to set all of the ConfigView's props at once.
+Notice that we've been able to set the `w` and `h` properties as attributes on the component. This sets them in the `props` object.
 
 Reload and see if the message is in the UI.
 
+### Render some controls
+
+It's time to get our ConfigView to render some real controls. Let's alter `gameConfig.tsx`.
+
+```tsx
+    render() {
+        return <div>
+          Cols: <input type="number" value={this.state.w} onChange={(e) => this.setW(+e.target.value)} />
+          Rows: <input type="number" value={this.state.h} onChange={(e) => this.setH(+e.target.value)} />
+          <button onClick={(e) => this.create()}>Create</button>  
+        </div>
+    }
+```
+
+In this, we've bound the input fields to render the `w` and `h` fields from the current state.
+
+We've then needed to bind an `onChange` handler for each field. This takes a lambda function.
+The parameter `(e)` is the event that has happened. And we're getting `e.target.value` from it to get the number out
+of the input field. This comes out as a string, even though the input field is for numbers. So in order to convert it
+to a number, we use `+e.target.value`. This then needs to get passed to `this.setW` and `this.setH` -- methods on our class. So we'd better write those!
+
+Likewise, we've boud the button's click handler to `this.create()`.
+
+Let's write `setW` and `setH`, which are methods on the `ConfigView` class:
+
+```tsx
+    setW(w:number) {
+        this.setState((state:ConfigState) => {
+            state.w = w
+            return state
+        })
+    }
+
+    setH(h:number) {
+        this.setState((state:ConfigState) => {
+            state.h = h
+            return state
+        })
+    }
+```
+
+In both cases, we call `setState`, which is a method from the React API (remember, `ConfigView` inherits from `React.Component`). `setState` gives you the current state, and asks you to return the new state. It will then automatically trigger re-rendering the component.
+
+Now let's write `create` method, and just get it to log to the console:
+
+```tsx
+    create() {
+        console.log("I was clicked")
+    }
+```
+
+Reload the page, and check the code so far works
+
+### Making it create the Life game
+
+Let's alter `render.ts`.
+
+First, import the ConfigProps interface
+
+```ts
+import { ConfigProps } from "./gameConfig"
+```
+
+and now let's make `life` a variable instead of a constant. We'll initialise it to a zero-sized game so it won't be null but won't be visible on the screen. And we'll define a `create` method that takes ConfigProps.
+
+```ts
+let life = new Life(0, 0)
+```
+
+```ts
+function create(config:ConfigProps) {
+    life = new Life(config.w, config.h)
+}
+```
+
+We'd best make sure those are exported:
+
+```ts
+export {
+    life as life,
+    render as render,
+    create as create
+}
+```
+
+Now let's go back to `gameConfig.tsx` and make it set up the game by altering what happens when we click the button
+
+First, let's import the functions from render.ts
+
+```tsx
+import { life, create, render} from "./render"
+```
+
+Then let's alter the `create` method on `ConfigView`:
+
+```tsx
+    create() {
+        create(this.state) // note this calls the function imported from render.ts
+        render()  // note this calls the function imported from render.ts
+    }
+```
+
+Reload, and check it works
+
+
+### Setting up the timer
+
+The last part we'll do is setting up the timer. In `gameConfig.tsx`, let's alter the render() method for our component:
+
+```tsx
+    render() {
+        return <div>
+          Cols: <input type="number" value={this.state.w} onChange={(e) => this.setW(+e.target.value)} /> 
+          Rows: <input type="number" value={this.state.h} onChange={(e) => this.setH(+e.target.value)} />   
+          <button onClick={(e) => this.create()}>Create</button>  
+
+          Period: <input type="number" value={this.state.period} onChange={(e) => this.setPeriod(+e.target.value)} />
+          <button onClick={(e) => this.toggleTimer()}>{ this.state.intervalId ? "Stop" : "Start" }</button>  
+        </div>
+```
+
+We've added an input field, very similar to the ones for `w` and `h` but operating on `period`.
+
+We've also added a button that will call `toggleTimer`, which we need to define. Its text is set by a ternary expression on whether `intervalId` in the component's state is defined.
+
+`toggleTimer` can work as another call to `setState`. Only this time, the call is also going to either start or cancel a JavaScript timer. We need to use `window.setInterval` because in Node setInterval has a different return type than in the browser -- calling `window.setInterval` makes sure TypeScript uses the browser's version.
+
+```ts
+    toggleTimer() {
+        this.setState((state:ConfigState) => {
+
+            if (state.intervalId) {
+                clearInterval(state.intervalId)
+                state.intervalId = undefined
+                return state
+            } else {
+                state.intervalId = window.setInterval(() => {
+                    life.stepGame()
+                    render()
+                }, this.state.period)
+                return state
+            }
+
+        })
+    }
+```
+
+Reload, and see if it all works!
\ No newline at end of file
-- 
GitLab