Skip to content

Commit

Permalink
Merge branch 'master' into refactor/version-compat
Browse files Browse the repository at this point in the history
  • Loading branch information
rschristian authored Dec 29, 2024
2 parents b37e754 + fb1999a commit ba216c7
Showing 1 changed file with 134 additions and 22 deletions.
156 changes: 134 additions & 22 deletions content/en/guide/v10/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,182 @@ description: 'Context allows you to pass props through intermediate components.

# Context

Context allows you to pass a value to a child deep down the tree without having to pass it through every component in-between via props. A very popular use case for this is theming. In a nutshell context can be thought of a way to do pub-sub-style updates in Preact.
Context is a way to pass data through the component tree without having to pass it through every component in-between via props. In a nutshell, it allows components anywhere in the hierarchy to subscribe to a value and get notified when it changes, bringing pub-sub-style updates to Preact.

There are two different ways to use context: Via the newer `createContext` API and the legacy context API. The difference between the two is that the legacy one can't update a child when a component inbetween aborts rendering via `shouldComponentUpdate`. That's why we highly recommend to always use `createContext`.
It's not uncommon to run into situations in which a value from a grandparent component (or higher) needs to be passed down to a child, often without the intermediate component needing it. This process of passing down props is often referred to as "prop drilling" and can be cumbersome, error-prone, and just plain repetitive, especially as the application grows and more values have to be passed through more layers. This is one of the key issues Context aims to address by providing a way for a child to subscribe to a value higher up in the component tree, accessing the value without it being passed down as a prop.

There are two ways to use context in Preact: via the newer `createContext` API and the legacy context API. These days there's very few reasons to ever reach for the legacy API but it's documented here for completeness.

---

<div><toc></toc></div>

---

## createContext
## Modern Context API

<!-- Load bearing comment, else marked eats the following heading -->

### Creating a Context

To create a new context, we use the `createContext` function. This function takes an initial state as an argument and returns an object with two component properties: `Provider`, to make the context available to descendants, and `Consumer`, to access the context value (primarily in class components).

```jsx
import { createContext } from "preact";

export const Theme = createContext("light");
export const User = createContext({ name: "Guest" });
export const Locale = createContext(null);
```

### Setting up a Provider

Once we've created a context, we must make it available to descendants using the `Provider` component. The `Provider` must be given a `value` prop, representing the initial value of the context.

> The initial value set from `createContext` is only used in the absence of a `Provider` above the consumer in the tree. This may be helpful for testing components in isolation, as it avoids the need for creating a wrapping `Provider` around your component.
```jsx
import { createContext } from "preact";

export const Theme = createContext("light");

function App() {
return (
<Theme.Provider value="dark">
<SomeComponent />
</Theme.Provider>
);
}
```

> **Tip:** You can have multiple providers of the same context throughout your app but only the closest one to the consumer will be used.
First we need to create a context object we can pass around. This is done via the `createContext(initialValue)` function. It returns a `Provider` component that is used to set the context value and a `Consumer` one which retrieves the value from the context.
### Using the Context

The `initialValue` argument is only used when a context does not have a matching `Provider` above it in the tree. This may be helpful for testing components in isolation, as it avoids the need for creating a wrapping `Provider`.
There are two ways to consume a context, largely depending on your preferred component style: `Consumer` (class components) and the `useContext` hook (function components/hooks).

<tab-group tabstring="Consumer, useContext">

```jsx
// --repl
import { render, createContext } from 'preact';
import { render, createContext } from "preact";

const SomeComponent = props => props.children;
// --repl-before
const Theme = createContext('light');
const ThemePrimary = createContext("#673ab8");

function ThemedButton(props) {
function ThemedButton() {
return (
<Theme.Consumer>
{theme => {
return <button {...props} class={'btn ' + theme}>Themed Button</button>;
}}
</Theme.Consumer>
<ThemePrimary.Consumer>
{theme => <button style={{ background: theme }}>Themed Button</button>}
</ThemePrimary.Consumer>
);
}

function App() {
return (
<Theme.Provider value="dark">
<ThemePrimary.Provider value="#8f61e1">
<SomeComponent>
<ThemedButton />
</SomeComponent>
</Theme.Provider>
</ThemePrimary.Provider>
);
}
// --repl-after
render(<App />, document.getElementById("app"));
```

> An easier way to use context is via the [useContext](/guide/v10/hooks#usecontext) hook.
```jsx
// --repl
import { render, createContext } from "preact";
import { useContext } from "preact/hooks";

const SomeComponent = props => props.children;
// --repl-before
const ThemePrimary = createContext("#673ab8");

function ThemedButton() {
const theme = useContext(ThemePrimary);
return <button style={{ background: theme }}>Themed Button</button>;
}

function App() {
return (
<ThemePrimary.Provider value="#8f61e1">
<SomeComponent>
<ThemedButton />
</SomeComponent>
</ThemePrimary.Provider>
);
}
// --repl-after
render(<App />, document.getElementById("app"));
```

</tab-group>

### Updating the Context

Static values can be useful, but more often than not, we want to be able to update the context value dynamically. To do so, we leverage standard component state mechanisms:

```jsx
// --repl
import { render, createContext } from "preact";
import { useContext, useState } from "preact/hooks";

const SomeComponent = props => props.children;
// --repl-before
const ThemePrimary = createContext(null);

function ThemedButton() {
const { theme } = useContext(ThemePrimary);
return <button style={{ background: theme }}>Themed Button</button>;
}

function ThemePicker() {
const { theme, setTheme } = useContext(ThemePrimary);
return (
<input
type="color"
value={theme}
onChange={e => setTheme(e.currentTarget.value)}
/>
);
}

function App() {
const [theme, setTheme] = useState("#673ab8");
return (
<ThemePrimary.Provider value={{ theme, setTheme }}>
<SomeComponent>
<ThemedButton />
{" - "}
<ThemePicker />
</SomeComponent>
</ThemePrimary.Provider>
);
}
// --repl-after
render(<App />, document.getElementById("app"));
```

## Legacy Context API

We include the legacy API mainly for backwards-compatibility reasons. It has been superseded by the `createContext` API. The legacy API has known issues like blocking updates if there are components in-between that return `false` in `shouldComponentUpdate`. If you nonetheless need to use it, keep reading.
This API is considered legacy and should be avoided in new code, it has known issues and only exists for backwards-compatibility reasons.

One of the key differences between this API and the new one is that this API cannot update a child when a component in-between the child and the provider aborts rendering via `shouldComponentUpdate`. When this happens, the child **will not** received the updated context value, often resulting in tearing (part of the UI using the new value, part using the old).

To pass down a custom variable through the context, a component needs to have the `getChildContext` method. There you return the new values you want to store in the context. The context can be accessed via the second argument in function components or `this.context` in a class-based component.
To pass down a value through the context, a component needs to have the `getChildContext` method, returning the intended context value. Descendants can then access the context via the second argument in function components or `this.context` in class-based components.

```jsx
// --repl
import { render } from 'preact';
import { render } from "preact";

const SomeOtherComponent = props => props.children;
// --repl-before
function ThemedButton(props, context) {
function ThemedButton(_props, context) {
return (
<button {...props} class={'btn ' + context.theme}>
<button style={{ background: context.theme }}>
Themed Button
</button>
);
Expand All @@ -77,7 +189,7 @@ function ThemedButton(props, context) {
class App extends Component {
getChildContext() {
return {
theme: 'light'
theme: "#673ab8"
}
}

Expand Down

0 comments on commit ba216c7

Please sign in to comment.