Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions content/en/guide/v10/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const App = <MyComponent name="John Doe" />;
render(App, document.body);
```

Functional components usually use Hooks to manage state, but Preact also provides [Signals](/guide/v10/signals). Signals are reactive values that can be used **inside or outside** components. When a signal changes, **only the component that use it will re-render**, not the whole page, and you don't need hooks. This makes signals a simpler and more efficient way to handle reactive state in modern Preact apps.

> Note in earlier versions they were known as `"Stateless Components"`. This doesn't hold true anymore with the [hooks-addon](/guide/v10/hooks).

## Class Components
Expand Down
185 changes: 180 additions & 5 deletions content/en/guide/v10/forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Often you'll want to collect user input in your application, and this is where `

To get started, we'll create a simple text input field that will update a state value as the user types. We'll use the `onInput` event to listen for changes to the input field's value and update the state per-keystroke. This state value is then rendered in a `<p>` element, so we can see the results.

<tab-group tabstring="Classes, Hooks">
<tab-group tabstring="Classes, Hooks, Signals">

```jsx
// --repl
Expand Down Expand Up @@ -68,11 +68,32 @@ function BasicInput() {
render(<BasicInput />, document.getElementById('app'));
```

```jsx
// --repl
import { render } from 'preact';
import { signal } from '@preact/signals';
// --repl-before
const name = signal('');

function BasicInput() {
return (
<div class="form-example">
<label>
Name: <input onInput={e => (name.value = e.currentTarget.value)} />
</label>
<p>Hello {name.value}</p>
</div>
);
}
// --repl-after
render(<BasicInput />, document.getElementById('app'));
```

</tab-group>

### Input (checkbox & radio)

<tab-group tabstring="Classes, Hooks">
<tab-group tabstring="Classes, Hooks, Signals">

```jsx
// --repl
Expand Down Expand Up @@ -194,11 +215,69 @@ function BasicRadioButton() {
render(<BasicRadioButton />, document.getElementById('app'));
```

```jsx
// --repl
import { render } from 'preact';
import { signal } from '@preact/signals';
// --repl-before
const allowContact = signal(false);
const contactMethod = signal('');

function BasicRadioButton() {
const toggleContact = () => (allowContact.value = !allowContact.value);
const setRadioValue = e => (contactMethod.value = e.currentTarget.value);

return (
<div class="form-example">
<label>
Allow contact: <input type="checkbox" onClick={toggleContact} />
</label>
<label>
Phone:{' '}
<input
type="radio"
name="contact"
value="phone"
onClick={setRadioValue}
disabled={!allowContact.value}
/>
</label>
<label>
Email:{' '}
<input
type="radio"
name="contact"
value="email"
onClick={setRadioValue}
disabled={!allowContact.value}
/>
</label>
<label>
Mail:{' '}
<input
type="radio"
name="contact"
value="mail"
onClick={setRadioValue}
disabled={!allowContact.value}
/>
</label>
<p>
You {allowContact.value ? 'have allowed' : 'have not allowed'} contact{' '}
{allowContact.value && ` via ${contactMethod.value}`}
</p>
</div>
);
}
// --repl-after
render(<BasicRadioButton />, document.getElementById('app'));
```

</tab-group>

### Select

<tab-group tabstring="Classes, Hooks">
<tab-group tabstring="Classes, Hooks, Signals">

```jsx
// --repl
Expand Down Expand Up @@ -251,6 +330,29 @@ function MySelect() {
render(<MySelect />, document.getElementById('app'));
```

```jsx
// --repl
import { render } from 'preact';
import { signal } from '@preact/signals';
// --repl-before
const value = signal('');

function MySelect() {
return (
<div class="form-example">
<select onChange={e => (value.value = e.currentTarget.value)}>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<p>You selected: {value.value}</p>
</form>
);
}
// --repl-after
render(<MySelect />, document.getElementById('app'));
```

</tab-group>

## Basic Forms
Expand All @@ -259,7 +361,7 @@ Whilst bare inputs are useful and you can get far with them, often we'll see our

To demonstrate, we'll create a new `<form>` element that contains two `<input>` fields: one for a user's first name and one for their last name. We'll use the `onSubmit` event to listen for the form submission and update the state with the user's full name.

<tab-group tabstring="Classes, Hooks">
<tab-group tabstring="Classes, Hooks, Signals">

```jsx
// --repl
Expand Down Expand Up @@ -333,6 +435,41 @@ function FullNameForm() {
render(<FullNameForm />, document.getElementById('app'));
```

```jsx
// --repl
import { render } from 'preact';
import { signal } from '@preact/signals';
// --repl-before
const fullName = signal('');

function FullNameForm() {
const onSubmit = e => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
fullName.value = formData.get('firstName') + ' ' + formData.get('lastName');
e.currentTarget.reset(); // Clear the inputs to prepare for the next submission
};

return (
<div class="form-example">
<form onSubmit={onSubmit}>
<label>
First Name: <input name="firstName" />
</label>
<label>
Last Name: <input name="lastName" />
</label>
<button>Submit</button>
</form>
{fullName.value && <p>Hello {fullName.value}</p>}
</div>
);
}

// --repl-after
render(<FullNameForm />, document.getElementById('app'));
```

</tab-group>

> **Note**: Whilst it's quite common to see React & Preact forms that link every input field to component state, it's often unnecessary and can get unwieldy. As a very loose rule of thumb, you should prefer using `onSubmit` and the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) API in most cases, using component state only when you need to. This reduces the complexity of your components and may skip unnecessary rerenders.
Expand Down Expand Up @@ -371,7 +508,7 @@ The problem with this is in the cases where the input fails that condition: beca

Here's an example of how you might use a controlled component to limit the number of characters in an input field:

<tab-group tabstring="Classes, Hooks">
<tab-group tabstring="Classes, Hooks, Signals">

```jsx
// --repl
Expand Down Expand Up @@ -449,4 +586,42 @@ const LimitedInput = () => {
render(<LimitedInput />, document.getElementById('app'));
```

```jsx
// --repl
import { render } from 'preact';
import { useSignal } from '@preact/signals';
import { useRef } from 'preact/hooks';
// --repl-before
const LimitedInput = () => {
const value = useSignal('');
const inputRef = useRef();

const onInput = e => {
if (e.currentTarget.value.length <= 3) {
value.value = e.currentTarget.value;
} else {
const start = inputRef.current.selectionStart;
const end = inputRef.current.selectionEnd;
const diffLength = Math.abs(
e.currentTarget.value.length - value.value.length
);
inputRef.current.value = value.value;
// Restore selection
inputRef.current.setSelectionRange(start - diffLength, end - diffLength);
}
};

return (
<div class="form-example">
<label>
This input is limited to 3 characters:{' '}
<input ref={inputRef} value={value} onInput={onInput} />
</label>
</div>
);
};
// --repl-after
render(<LimitedInput />, document.getElementById('app'));
```

</tab-group>
9 changes: 8 additions & 1 deletion content/en/guide/v10/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Hooks in Preact allow you to compose behaviours together and re-use

# Hooks

The Hooks API is an alternative way to write components in Preact. Hooks allow you to compose state and side effects, reusing stateful logic much more easily than with class components.
The Hooks API is an alternative way to write components in Preact. Hooks allow you to compose state and side effects, reusing stateful logic much more easily than with class components. In addition to hooks, Preact also provides [Signals](/guide/v10/signals), Signals are another way to manage state.

If you've worked with class components in Preact for a while, you may be familiar with patterns like "render props" and "higher order components" that try to solve these challenges. These solutions have tended to make code harder to follow and more abstract. The hooks API makes it possible to neatly extract the logic for state and side effects, and also simplifies unit testing that logic independently from the components that rely on it.

Expand Down Expand Up @@ -194,6 +194,8 @@ const Counter = () => {
render(<Counter />, document.getElementById('app'));
```

Instead of `useState`, you can use [Signals](/guide/v10/signals). With signals, you don’t need a setter function. While the value changes, the component updates by itself.

> When our initial state is expensive it's better to pass a function instead of a value.

### useReducer
Expand Down Expand Up @@ -237,6 +239,8 @@ function Counter() {
render(<Counter />, document.getElementById('app'));
```

The `useReducer` hook is used for managing complex state, but Preact [Signals](guide/v10/signals) can do the same thing more simply. They automatically update the components that use them, without needing a reducer or dispatch function.

## Memoization

In UI programming there is often some state or result that's expensive to calculate. Memoization can cache the results of that calculation allowing it to be reused when the same input is used.
Expand Down Expand Up @@ -630,3 +634,6 @@ function App() {
}, [dependencies]);
}
```

> Using hooks are the usual way to handle state and side effects in functional components.
> **But keep in mind:** Preact also has [Signals](/guide/v10/signals), which will do the same job in an easier way.
39 changes: 38 additions & 1 deletion content/en/guide/v10/refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Refs aren't limited to storing DOM nodes, however; they can be used to store any

In the following example, we store the ID of an interval in a ref to be able to start & stop it independently.

<tab-group tabstring="Classes, Hooks">
<tab-group tabstring="Classes, Hooks, Signals">

```jsx
// --repl
Expand Down Expand Up @@ -229,4 +229,41 @@ function SimpleClock() {
render(<SimpleClock />, document.getElementById('app'));
```

```jsx
// --repl
import { render } from 'preact';
import { signal, computed } from '@preact/signals';
import { useRef } from 'preact/hooks';
// --repl-before
function SimpleClock() {
const time = signal(Date.now());
const intervalId = useRef(null);

const startClock = () => {
time.value = Date.now();
intervalId.current = setInterval(() => {
time.value = Date.now();
}, 1000);
};

const stopClock = () => {
clearInterval(intervalId.current);
};

const formattedTime = computed(() =>
new Date(time.value).toLocaleTimeString()
);

return (
<div>
<button onClick={startClock}>Start Clock</button>
<time dateTime={formattedTime}>{formattedTime}</time>
<button onClick={stopClock}>Stop Clock</button>
</div>
);
}
// --repl-after
render(<SimpleClock />, document.getElementById('app'));
```

</tab-group>
2 changes: 1 addition & 1 deletion content/en/guide/v10/signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Composable reactive state with automatic rendering

# Signals

Signals are reactive primitives for managing application state.
Signals are reactive primitives for managing application state. They hold a value and automatically update the parts of the app that use it whenever the value changes.

What makes Signals unique is that state changes automatically update components and UI in the most efficient way possible. Automatic state binding and dependency tracking allows Signals to provide excellent ergonomics and productivity while eliminating the most common state management footguns.

Expand Down
2 changes: 1 addition & 1 deletion content/en/tutorial/08-keys.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Keys
prev: /tutorial/07-side-effects
next: /tutorial/09-error-handling
next: /tutorial/09-signals
solvable: true
---

Expand Down
Loading