Add basic notes on React TS
This commit is contained in:
parent
15a3f410be
commit
21ae762c9e
6 changed files with 360 additions and 0 deletions
144
Programming_Languages/React/Hooks/Forms.md
Normal file
144
Programming_Languages/React/Hooks/Forms.md
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Programming_Languages
|
||||||
|
- javascript
|
||||||
|
- react
|
||||||
|
- react-hooks
|
||||||
|
---
|
||||||
|
|
||||||
|
# Forms using hooks
|
||||||
|
|
||||||
|
With hooks, form processing is exactly the same as [classes](/Programming_Languages/React/Classes/Forms.md) in terms of the overall methodology, but the syntax is slightly different as a result of the `useState` hook.
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
## Basic approach
|
||||||
|
|
||||||
|
Instead of using `this.state` and `this.setState` . We just have the `useState` hook. But the controlled component principle is the same. Let's say we have a simple email input:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
```
|
||||||
|
|
||||||
|
As this is a form, the state change is going to be the result of user input. So we need to prep our form to enable this.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input type="text" value="{email}" onChange="{setEmail}" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we just need to make good on the `setEmail` method we declared when we initialised the state:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const handleChange = (event) => {
|
||||||
|
setEmail(event.target.value);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Applied example
|
||||||
|
|
||||||
|
Here is an applied example of the above approach for a form that has three input fields. This component outputs the inputs as JSON on submit:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
|
||||||
|
function FormHook() {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [phone, setPhone] = useState("");
|
||||||
|
const [age, setAge] = useState("");
|
||||||
|
const [formOutput, setFormOutput] = useState("Form output");
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setFormOutput(
|
||||||
|
JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input type="text" value={email} onChange={(event) => setEmail(event.target.value)}>
|
||||||
|
<input type="text" value={phone} onChange={(event) => setPhone(event.target.value)}>
|
||||||
|
<input type="number" value={age} onChange={(event) => setAge(event.target.value)}>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## More complex forms
|
||||||
|
|
||||||
|
The above is fine if you only have one form with a couple of inputs. But if you are managing multiple forms or forms with a complex array of inputs, you would need to create `useState` declaration for every single input with a custom `onChange` event for each one which is repetitious and not very clean.
|
||||||
|
|
||||||
|
So instead of this, just like with class-based controlled components, we use the `name` HTML attribute to distinguish each input and create a generic `onChange` function that distinguishes each separate input by destructuring a key, value object using the `name`.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input type="text" name="email" value={formValues.email} onChange={handleChange}>
|
||||||
|
<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
|
||||||
|
<input type="number" name="age" value={formValues.age} onChange={handleChange}>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const initialState = {
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
age: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const [formValues, setFormValues] = useState(initialState);
|
||||||
|
|
||||||
|
const handleChange = (event) => {
|
||||||
|
const {name, value} = event.target;
|
||||||
|
setFormValues({...formValues, [name]: value});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
There are three parts:
|
||||||
|
|
||||||
|
1. First we create the initial state.
|
||||||
|
2. Next we store this initial state as the variable in the `useState` initialisation: `formValues` . We also provide a method `setFormValues` which will be used by the change handler to log the user's inputs.
|
||||||
|
3. Finally we create the function that will log the user changes. First we use object destructuring on the change event to enable us to retrieve the `name` and `value` attributes of the HTML inputs in the component. Then we use spread syntax to say that for each input pair, retrieve its value, using the destructured `name` variable as the key.
|
||||||
|
|
||||||
|
### Applied example
|
||||||
|
|
||||||
|
Below I have updated the previous context to this time reflect the new, abstracted logic:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function FormHookAbstracted() {
|
||||||
|
const initialState = {
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
age: "",
|
||||||
|
};
|
||||||
|
const [formValues, setFormValues] = useState(initialState);
|
||||||
|
const handleChange = (event) => {
|
||||||
|
const { name, value } = event.target;
|
||||||
|
setFormValues({ ...formValues, [name]: value });
|
||||||
|
};
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setFormOutput(
|
||||||
|
JSON.stringify({ email: email, phone: phone, age: age }, null, 2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input type="text" name="email" value={formValues.email} onChange={handleChange}>
|
||||||
|
<input type="text" name="phone" value={formValues.phone} onChange={handleChange}>
|
||||||
|
<input type="number" name="age" value={formValues.age} onChange={handleChange}>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
export default FormHookAbstracted;
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that instead of individual variables `email` , `phone`, `age` , this approach returns a single object `formValues` . We could therefore access the individual values with e.g `[formValues.email](http://formvalues.email)` .
|
||||||
|
|
||||||
|
As it is an object, it makes resetting to the original state very easy, viz:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.values(formValues).map((x) => setFormValues(initialState));
|
||||||
|
};
|
||||||
|
```
|
73
Programming_Languages/React/React_Typescript/Events.md
Normal file
73
Programming_Languages/React/React_Typescript/Events.md
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Programming_Languages
|
||||||
|
- typescript
|
||||||
|
- react
|
||||||
|
---
|
||||||
|
|
||||||
|
# Events
|
||||||
|
|
||||||
|
Building on the previous examples for React TypeScript we are going to add a simple form that enables the user to add people to the list. This will demonstrate how we type components that use event handlers.
|
||||||
|
|
||||||
|
We are going to use the preexisting interface for recording the list items:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface IState {
|
||||||
|
people: {
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Our form:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {IState as Props};
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
people: Props["people"]
|
||||||
|
setPeople: React.Dispatch<React.SetStateAction<Props["people"]>>
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddToList: React.FC<IProps> = () => {
|
||||||
|
const [people, setPeople] = useState<IState["people"]>({})
|
||||||
|
const [formVals, setFormVals] = useState({});
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setFormValues({
|
||||||
|
...input,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (): void => {
|
||||||
|
if (!input.name || !input.age) return
|
||||||
|
|
||||||
|
setPeople({
|
||||||
|
...people,
|
||||||
|
{
|
||||||
|
name: input.name,
|
||||||
|
age: input.age
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form>
|
||||||
|
<input type="text" name="name" value={input.name} onChange={handleChange} />
|
||||||
|
<input type="text" name="age" value={input.age} onChange={handleChange} />
|
||||||
|
</form>
|
||||||
|
<button onClick={handleClick}>Add to list</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This follows standard practise for [controlled-components](/Programming_Languages/React/Hooks/Forms.md). The TS specific additions:
|
||||||
|
|
||||||
|
- We define the change event as being of the type `React.ChangeEvent` and state that it corresponds to a generic - `HTMLInputElement`. So we are saying that whenever this function is called we must be passing it an input element so that we can extract the event associated with its `target` property.
|
||||||
|
|
||||||
|
- We are passing around variations on the `IState` interface in order to type the values that we are adding to the people array.
|
39
Programming_Languages/React/React_Typescript/Functions.md
Normal file
39
Programming_Languages/React/React_Typescript/Functions.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Programming_Languages
|
||||||
|
- typescript
|
||||||
|
- react
|
||||||
|
---
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
|
||||||
|
Continuing from the other examples of React Typescript, we could do standard listing function, like:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<ul>
|
||||||
|
{people.map((person) => {
|
||||||
|
return <li>{person.name}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
But it's neater to do it with a function defined within the `List` component:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const renderList = (): JSX.Element[] => {
|
||||||
|
return people.map((person) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<div>{person.name}</div>
|
||||||
|
<div>{person.age}</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
And then change the eariler list to a function invocation:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<ul>{renderList()}<ul>
|
||||||
|
```
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Programming_Languages
|
||||||
|
- typescript
|
||||||
|
- react
|
||||||
|
---
|
||||||
|
|
||||||
|
# Managing state
|
||||||
|
|
||||||
|
## Basic: `useState`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const [amount, setAmount] = useState<number | string>(3);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom type
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface IState {
|
||||||
|
people: IPerson[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPerson {
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [people, setPeople] = useState<IState>({});
|
||||||
|
|
||||||
|
// Alternative declaration
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
people: {
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [people, setPeople] = useState<IState['people']>({});
|
||||||
|
```
|
38
Programming_Languages/React/React_Typescript/Props.md
Normal file
38
Programming_Languages/React/React_Typescript/Props.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Programming_Languages
|
||||||
|
- typescript
|
||||||
|
- react
|
||||||
|
---
|
||||||
|
|
||||||
|
# Props
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface IProps {
|
||||||
|
people: {
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
note?: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const List: React.FC<IProps> = ({people}: IProps) => {
|
||||||
|
return()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note we say that the props into the func component are of type IProps
|
||||||
|
// And we destructure the people key
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in the parent:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
|
||||||
|
const [people, setPeople] = useState<IState['people']>({});
|
||||||
|
|
||||||
|
<List props={people}>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
<p style="color: red;">Should I use type or interface? What is consensus?</p>
|
|
@ -34,3 +34,29 @@ const store: string[] = []; // Empty array
|
||||||
`Object` is a valid type declaration in TS but it is not particularly helpful since it becomes similar to using [any](./Any.md) given that most primitive types in JavaScripts prototypically inherit from an Object.
|
`Object` is a valid type declaration in TS but it is not particularly helpful since it becomes similar to using [any](./Any.md) given that most primitive types in JavaScripts prototypically inherit from an Object.
|
||||||
|
|
||||||
Generally, when you use objects in TypeScript you type them as [custom types](./Custom_types.md).
|
Generally, when you use objects in TypeScript you type them as [custom types](./Custom_types.md).
|
||||||
|
|
||||||
|
## Array of (untyped) objects
|
||||||
|
|
||||||
|
If we just know that it is going to be an array of objects we can use:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const arrOfObj = {}[]
|
||||||
|
```
|
||||||
|
|
||||||
|
If we wish to define a particular shape but without defining a type:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const arrOfObj = { name: string, age: number }[]
|
||||||
|
```
|
||||||
|
|
||||||
|
But better for reusability to do:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ArrayOfObj = {
|
||||||
|
name: string,
|
||||||
|
age: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrOfObj: ArrayOfObj[] = [{}, ...]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
Loading…
Add table
Reference in a new issue