diff --git a/content/en/guide/v10/context.md b/content/en/guide/v10/context.md index 0bbc04915..f630f7680 100644 --- a/content/en/guide/v10/context.md +++ b/content/en/guide/v10/context.md @@ -5,9 +5,11 @@ 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. --- @@ -15,60 +17,170 @@ There are two different ways to use context: Via the newer `createContext` API a --- -## createContext +## Modern Context API + + + +### 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 ( + + + + ); +} +``` + +> **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). + + ```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 => { - return ; - }} - + + {theme => } + ); } function App() { return ( - + - + ); } // --repl-after render(, 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 ; +} + +function App() { + return ( + + + + + + ); +} +// --repl-after +render(, document.getElementById("app")); +``` + + + +### 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 ; +} + +function ThemePicker() { + const { theme, setTheme } = useContext(ThemePrimary); + return ( + setTheme(e.currentTarget.value)} + /> + ); +} + +function App() { + const [theme, setTheme] = useState("#673ab8"); + return ( + + + + {" - "} + + + + ); +} +// --repl-after +render(, 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 ( - ); @@ -77,7 +189,7 @@ function ThemedButton(props, context) { class App extends Component { getChildContext() { return { - theme: 'light' + theme: "#673ab8" } }