Skip to content

Commit 9939a3b

Browse files
committed
03 state migrated
1 parent d595927 commit 9939a3b

File tree

10 files changed

+350
-0
lines changed

10 files changed

+350
-0
lines changed

hooks/03_State/.babelrc

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"presets": [
3+
[
4+
"@babel/preset-env",
5+
{
6+
"useBuiltIns": "entry"
7+
}
8+
]
9+
]
10+
}

hooks/03_State/Readme.md

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# 03 State
2+
3+
In this example we introduce a basic React concept: handling State.
4+
5+
In this scenario we provide a default username and let the user update it.
6+
7+
We take as a starting point the example _02 Properties_:
8+
9+
## Summary steps:
10+
11+
- Create an _App_ component that holds the state. This state will contain the current
12+
username (with default value "defaultUserName").
13+
This _App_ component renders the _Hello_ component. At first we create a simple stateless
14+
_App_ component.
15+
- Update _main.tsx_ file to include our _App_ component.
16+
- Change _App_ component to a stateful class component to hold the _userName_ state.
17+
- Create a _NameEdit_ component to let the user change the value of username. This changes the _App_ state
18+
using a function from _App_.
19+
- Check everything works properly.
20+
21+
## Prerequisites
22+
23+
Install [Node.js and npm](https://nodejs.org) if they are not already installed on your computer.
24+
25+
> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors.
26+
27+
## Steps to build it
28+
29+
- Copy the content from _02 Properties_ and execute `npm install`.
30+
31+
- Let's create an _App_ component under a new file named _app.tsx_ (this component will display the _Hello_ component).
32+
33+
_./src/app.tsx_
34+
35+
```jsx
36+
import * as React from "react";
37+
import { HelloComponent } from "./hello";
38+
39+
export const App = () => {
40+
return <HelloComponent userName="John" />;
41+
};
42+
```
43+
44+
- Let's update _index.tsx_ just to use the _App_ component that we have just created.
45+
46+
_./src/index.tsx_
47+
48+
```diff
49+
import * as React from 'react';
50+
import * as ReactDOM from 'react-dom';
51+
+ import { App } from './app';
52+
53+
- import { HelloComponent } from './hello';
54+
55+
ReactDOM.render(
56+
- <HelloComponent userName="John" />,
57+
+ <App />,
58+
document.getElementById('root')
59+
);
60+
```
61+
62+
- Now we can check that things are still working as expected.
63+
64+
```
65+
npm start
66+
```
67+
68+
- It's time to revisit _app.tsx_. We want to store the user's name and let the user updated it. We will use hooks to
69+
allow _App_ fucntional components to make use of state (this works in React 16.8.2 and above if you have to use
70+
older verions you have to use a class component, check the "old*classes_components" on the root of this repo for example).
71+
We will add \_userName* to the state.
72+
73+
Let's move this component to a class stateful component and define a state including _userName_, and pass this value to the _Hello_ component.
74+
75+
_./src/app.tsx_
76+
77+
```diff
78+
import * as React from "react";
79+
80+
import { HelloComponent } from "./hello";
81+
82+
export const App = () => {
83+
+ const [name, setName] = React.useState('defaultUserName');
84+
- return <HelloComponent userName="John" />;
85+
+ return <HelloComponent userName={name} />;
86+
};
87+
```
88+
89+
- Again, we can do a quick check to test that everything works as expected.
90+
91+
```
92+
npm start
93+
```
94+
95+
- Now it's time to create an _NameEdit_ component. This component lets the user update his username and notifies with a callback to the parent control whenever the value of _userName_ gets updated.
96+
97+
_./src/nameEdit.tsx_
98+
99+
```jsx
100+
import * as React from "react";
101+
102+
interface Props {
103+
userName: string;
104+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
105+
}
106+
107+
export const NameEditComponent = (props: Props) => (
108+
<>
109+
<label>Update name:</label>
110+
<input value={props.userName} onChange={props.onChange} />
111+
</>
112+
);
113+
```
114+
115+
Side note: What is this Fragment or <> stuff? A way to create component that has multiple root elements (not a single parent). Available from React 16.2. As an alternative you can type:
116+
117+
```jsx
118+
...
119+
export const NameEditComponent = (props : Props) =>
120+
<React.Fragment>
121+
<label>Update name:</label>
122+
<input value={props.userName}
123+
onChange={props.onChange}
124+
/>
125+
</React.Fragment>
126+
}
127+
```
128+
129+
- In the _app.tsx_ file, let's add a function to replace the state value of _userName_ with the new one.
130+
131+
_./src/app.tsx_
132+
133+
```diff
134+
import * as React from "react";
135+
import { HelloComponent } from "./hello";
136+
import { NameEditComponent } from './nameEdit';
137+
import { NameEditComponent } from './nameEdit';
138+
139+
140+
export const App = () => {
141+
const [name, setName] = React.useState("defaultUserName");
142+
143+
+ const setUsernameState = (event: React.ChangeEvent<HTMLInputElement>) => {
144+
+ setName(event.target.value);
145+
+ }
146+
147+
- return <HelloComponent userName={name} />;
148+
+ return (
149+
+ <>
150+
+ <HelloComponent userName={name} />
151+
+ <NameEditComponent userName={name} onChange={setUsernameState} />
152+
+ </>
153+
+ );
154+
};
155+
```
156+
157+
Side note: mind the use of the fat arrow function. This avoids losing the context for _this_ in the callback.
158+
159+
- Finally let's test everything works once more.
160+
161+
```
162+
npm start
163+
```

hooks/03_State/package.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "react-typescript-by-sample",
3+
"version": "1.0.0",
4+
"description": "React Typescript examples",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "webpack-dev-server --mode development --inline --hot --open",
8+
"build": "webpack --mode development"
9+
},
10+
"keywords": [
11+
"react",
12+
"typescript",
13+
"hooks"
14+
],
15+
"author": "Braulio Diez Botella",
16+
"license": "MIT",
17+
"devDependencies": {
18+
"@babel/cli": "^7.2.3",
19+
"@babel/core": "^7.2.2",
20+
"@babel/polyfill": "^7.2.5",
21+
"@babel/preset-env": "^7.3.1",
22+
"@types/react": "^16.8.3",
23+
"@types/react-dom": "^16.8.1",
24+
"awesome-typescript-loader": "^5.2.1",
25+
"babel-loader": "^8.0.5",
26+
"css-loader": "^2.1.0",
27+
"file-loader": "^3.0.1",
28+
"html-webpack-plugin": "^3.2.0",
29+
"mini-css-extract-plugin": "^0.5.0",
30+
"style-loader": "^0.23.1",
31+
"typescript": "^3.3.3",
32+
"url-loader": "^1.1.2",
33+
"webpack": "^4.29.3",
34+
"webpack-cli": "^3.2.3",
35+
"webpack-dev-server": "^3.1.14"
36+
},
37+
"dependencies": {
38+
"react": "^16.8.2",
39+
"react-dom": "^16.8.2"
40+
}
41+
}

hooks/03_State/src/app.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as React from "react";
2+
import { HelloComponent } from "./hello";
3+
import { NameEditComponent } from "./nameEdit";
4+
5+
export const App = () => {
6+
const [name, setName] = React.useState("defaultUserName");
7+
8+
const setUsernameState = (event: React.ChangeEvent<HTMLInputElement>) => {
9+
setName(event.target.value);
10+
};
11+
12+
return (
13+
<>
14+
<HelloComponent userName={name} />
15+
<NameEditComponent userName={name} onChange={setUsernameState} />
16+
</>
17+
);
18+
};

hooks/03_State/src/hello.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as React from "react";
2+
3+
interface Props {
4+
userName: string;
5+
}
6+
7+
export const HelloComponent = (props: Props) => {
8+
return <h2>Hello user: {props.userName} !</h2>;
9+
};

hooks/03_State/src/index.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title></title>
6+
</head>
7+
<body>
8+
<div class="well">
9+
<h1>Sample app</h1>
10+
<div id="root"></div>
11+
</div>
12+
</body>
13+
</html>

hooks/03_State/src/index.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as React from "react";
2+
import * as ReactDOM from "react-dom";
3+
4+
import { App } from "./app";
5+
6+
ReactDOM.render(<App />, document.getElementById("root"));

hooks/03_State/src/nameEdit.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as React from "react";
2+
3+
interface Props {
4+
userName: string;
5+
onChange: (e : React.ChangeEvent<HTMLInputElement>) => void;
6+
}
7+
8+
export const NameEditComponent = (props: Props) => (
9+
<>
10+
<label>Update name:</label>
11+
<input value={props.userName} onChange={props.onChange} />
12+
</>
13+
);

hooks/03_State/tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es6",
4+
"module": "es6",
5+
"moduleResolution": "node",
6+
"declaration": false,
7+
"noImplicitAny": false,
8+
"jsx": "react",
9+
"sourceMap": true,
10+
"noLib": false,
11+
"suppressImplicitAnyIndexErrors": true
12+
},
13+
"compileOnSave": false,
14+
"exclude": ["node_modules"]
15+
}

hooks/03_State/webpack.config.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
var HtmlWebpackPlugin = require("html-webpack-plugin");
2+
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
3+
var webpack = require("webpack");
4+
var path = require("path");
5+
6+
var basePath = __dirname;
7+
8+
module.exports = {
9+
context: path.join(basePath, "src"),
10+
resolve: {
11+
extensions: [".js", ".ts", ".tsx"]
12+
},
13+
entry: ["@babel/polyfill", "./index.tsx"],
14+
output: {
15+
path: path.join(basePath, "dist"),
16+
filename: "bundle.js"
17+
},
18+
devtool: "source-map",
19+
devServer: {
20+
contentBase: "./dist", // Content base
21+
inline: true, // Enable watch and live reload
22+
host: "localhost",
23+
port: 8080,
24+
stats: "errors-only"
25+
},
26+
module: {
27+
rules: [
28+
{
29+
test: /\.(ts|tsx)$/,
30+
exclude: /node_modules/,
31+
loader: "awesome-typescript-loader",
32+
options: {
33+
useBabel: true,
34+
babelCore: "@babel/core" // needed for Babel v7
35+
}
36+
},
37+
{
38+
test: /\.css$/,
39+
use: [MiniCssExtractPlugin.loader, "css-loader"]
40+
},
41+
{
42+
test: /\.(png|jpg|gif|svg)$/,
43+
loader: "file-loader",
44+
options: {
45+
name: "assets/img/[name].[ext]?[hash]"
46+
}
47+
}
48+
]
49+
},
50+
plugins: [
51+
//Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin
52+
new HtmlWebpackPlugin({
53+
filename: "index.html", //Name of file in ./dist/
54+
template: "index.html", //Name of template in ./src
55+
hash: true
56+
}),
57+
new MiniCssExtractPlugin({
58+
filename: "[name].css",
59+
chunkFilename: "[id].css"
60+
})
61+
]
62+
};

0 commit comments

Comments
 (0)