Skip to content

Commit

Permalink
Update context docs (#2570)
Browse files Browse the repository at this point in the history
* update context docs

* fix doc test
  • Loading branch information
hamza1311 committed Apr 3, 2022
1 parent b433074 commit 4bc61b8
Showing 1 changed file with 96 additions and 26 deletions.
122 changes: 96 additions & 26 deletions website/docs/concepts/contexts.mdx
@@ -1,12 +1,25 @@
---
title: "Contexts"
sidebar_label: Contexts
description: "Using contexts to pass data within application"
description: "Using contexts to pass deeply nested data"
---

Generally data is passed down the component tree using props but that becomes tedious for values such as
user preferences, authentication information etc. Consider the following example which passes down the
theme using props:
Usually, data is passed from a parent component to a child component via props.
But passing props can become verbose and annoying if you have to pass them through many components in the middle,
or if many components in your app need the same information. Context solve this problem by allowing a
parent component to make data available to *any* component in the tree below it, no matter how deep,
without having to pass it down with props.

## The problem with props: "Prop Drilling"

Passing [props](./function-components/properties) is a great way to pass data directly from parent to a child.
They become cumbersome to pass down through deeply nested component tree or when multiple components share the same data.
A common solution to data sharing is lifting the data to a common ancestor and making the children take it as props.
However, this can lead to cases where the prop has to go through multiple components in order to reach the component needs it.
This situation is called "Prop Drilling".


Consider the following example which passes down the theme using props:

```rust
use yew::{html, Children, Component, Context, Html, Properties, function_component};
Expand Down Expand Up @@ -48,48 +61,76 @@ fn Title(_props: &ThemeProps) -> Html {
// impl
}
}

#[function_component]
fn NavButton(_props: &ThemeProps) -> Html {
html! {
// impl
}
}

// root
let theme = Theme {
foreground: "yellow".to_owned(),
background: "pink".to_owned(),
};
/// App root
#[function_component]
fn App() -> Html {
let theme = Theme {
foreground: "yellow".to_owned(),
background: "pink".to_owned(),
};

html! {
<Navbar {theme} />
};
html! {
<Navbar {theme} />
}
}
```

Passing down data like this isn't ideal for something like a theme which needs to be available everywhere.
This is where contexts come in.

Contexts can be understood as a global-ish opt in props.
You need to provide the value somewhere in the component tree and all sub-components will be able to listen into its value and changes.
We "drill" the theme prop through `Navbar` so that it can reach `Title` and `NavButton`.
It would be nice if `Title` and `NavButton`, the components that need access to the theme, can just access the theme
without having to pass it to them as prop. Contexts solve this problem by allowing a parent to pass data, theme in this case,
to its children.

## Using Contexts

In order to use contexts, we need a struct which defines what data is to be passed.
For the above use-case, consider the following struct:
### Step 1: Providing the context

A context provider is required to consume the context. `ContextProvider<T>`, where `T` is the context struct is used as the provider.
`T` must implement `Clone` and `PartialEq`. `ContextProvider` is the component whose children will have the context available to them.
The children are re-rendered when the context changes. A struct is used to define what data is to be passed. The `ContextProvider` can be used as:

```rust
use std::rc::Rc;
use yew::prelude::*;

#[derive(Clone, Debug, PartialEq)]
struct Theme {
foreground: String,
background: String,
}
```

A context provider is required to consume the context. `ContextProvider<T>`, where `T` is the context struct is used as the provider.
`T` must implement `Clone` and `PartialEq`. `ContextProvider` is the component whose children will have the context available to them.
The children are re-rendered when the context changes.
#[function_component]
fn NavButton() -> Html {
let theme = use_context::<Theme>();

html! {
// use theme
}
}

#[function_component]
fn App() -> Html {
let theme = use_memo(|_| Theme {
foreground: "yellow".to_owned(),
background: "pink".to_owned(),
}, ());

html! {
<ContextProvider<Rc<Theme>> context={theme}>
<NavButton />
</ContextProvider<Rc<Theme>>>
}
}
```

### Consuming context
### Step 2: Consuming context

#### Function components

Expand All @@ -100,6 +141,35 @@ See [docs for use_context](function-components/hooks/use-context) to learn more.

We have 2 options to consume contexts in struct components:

- [Higher Order Components](../advanced-topics/struct-components/hoc)
A higher order function component will consume the context and pass the data to the struct component which requires it.
- [Higher Order Components](../advanced-topics/struct-components/hoc): A higher order function component will consume the context and pass the data to the struct component which requires it.
- Consume context directly in struct component. See [example of struct component as a consumer](https://github.com/yewstack/yew/tree/master/examples/contexts/src/struct_component_subscriber.rs)

## Use cases

Generally, if some data is needed by distant components in different parts of the tree, it's likely that context will help you.
Here's some examples of such cases:

- **Theming**: You can put a context at the top of the app that holds your app theme and use it to adjust the visual appearance, as shown in the above example.
- **Current user account**: In many cases, components need to know the current logged-in user. You can use a context to provide the current user object to the components.

### Considerations to make before using contexts

Contexts are very easy to use. That makes them very easy to misuse/overuse.
Just because you can use a context to share props to components multiple levels deep, doesn't mean that you should.

For example, you may be able to extract a component and pass that component as a child to another component. For example,
you may have a `Layout` component which takes `articles` as prop and passes it down to `ArticleList` component.
You should refactor the `Layout` component to take children as props and display `<Layout> <ArticleList {articles} /> </Layout>`.

## Mutating context value a child

Because of Rust's ownership rules, a context cannot have a method that takes `&mut self` that can be called by children.
In order to mutate a context's value, we must combine it with a reducer. This is done by using the
[`use_reducer`](./function_component/hooks/use_reducer) hook.

The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts) demonstrates mutable contexts
with the help of contexts

## Further reading

- The [contexts example](https://github.com/yewstack/yew/tree/master/examples/contexts)

1 comment on commit 4bc61b8

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yew master branch benchmarks (Lower is better)

Benchmark suite Current: 4bc61b8 Previous: b433074 Ratio
yew-struct-keyed 01_run1k 213.8005 183.1295 1.17
yew-struct-keyed 02_replace1k 235.4005 192.0025 1.23
yew-struct-keyed 03_update10th1k_x16 400.2185 348.49199999999996 1.15
yew-struct-keyed 04_select1k 72.283 76.432 0.95
yew-struct-keyed 05_swap1k 90.8485 97.895 0.93
yew-struct-keyed 06_remove-one-1k 35.832499999999996 29.844 1.20
yew-struct-keyed 07_create10k 3127.124 2438.6025 1.28
yew-struct-keyed 08_create1k-after1k_x2 569.808 434.0485 1.31
yew-struct-keyed 09_clear1k_x8 245.545 200.189 1.23
yew-struct-keyed 21_ready-memory 1.4005584716796875 1.4005584716796875 1
yew-struct-keyed 22_run-memory 1.6623420715332031 1.664592742919922 1.00
yew-struct-keyed 23_update5-memory 1.7012214660644531 1.7010536193847656 1.00
yew-struct-keyed 24_run5-memory 1.7113533020019531 1.710693359375 1.00
yew-struct-keyed 25_run-clear-memory 1.3290328979492188 1.421672821044922 0.93
yew-struct-keyed 31_startup-ci 1732.546 1730.058 1.00
yew-struct-keyed 32_startup-bt 32.227999999999994 29.254 1.10
yew-struct-keyed 34_startup-totalbytes 330.548828125 330.548828125 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.