Add basic notes on React TS

This commit is contained in:
thomasabishop 2022-07-19 10:12:15 +01:00
parent 15a3f410be
commit 21ae762c9e
6 changed files with 360 additions and 0 deletions

View 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));
};
```

View 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.

View 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>
```

View file

@ -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']>({});
```

View 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>

View file

@ -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.
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[] = [{}, ...]
```