docs: add documentation on using the createContext function to share state between components
🙋 Documentation Request
I'm writing a book on web components that contains a chapter devoted to FAST.
I want to show the recommended approach for sharing state between FAST web components.
Can someone point me to some documentation on the createContext function or an example of using it?
@janechu I see you are an active contributor to FAST. Can you point me to some documentation or an example of using the createcContext function. As I said above, I'm writing a book on web components and comparing many approaches for developing them. I have a separate chapter for each, including a chapter on FAST. I have an example of sharing state between components for each library (Lit, Stencil, Solid, and wrec), not yet for FAST and I don't one that to be left out.
@mvolkmann, @janechu is out of office for a bit so let's not bother her directly. Generally a bad habit to ping maintainers directly unless it's serious. We got your issue on Friday and have it in the queue for triage (see the status label). We understand that you're probably in a hurry to finish your chapter but we have other priorities to balance as well.
createContext is part of the Dependency Injection API which is documented here: https://fast.design/docs/advanced/dependency-injection
Generally speaking, DI is not an API we reach for often (found zero references to it on my computer outside of FAST). It might be helpful to know more about your example because there's a few ways to pass around state in FAST components.
The most common pattern is if you're doing simple child asking parent for context in a template, you can get away with the parent context passed by the template.
const renderFooFromParentTemplate = html`
<div>${ (x, c) => c.parentContext.foo}</div>
`
Then we'd typically use an Events Up / Data-Down one-way data flow pattern (see: using $emit). But FASTElement does support things like two-way data flow and MVVM.
My apologies for directly pinging a maintainer. I assumed the answer would be a single URL that an active maintainer would have handy. I shouldn't have assumed that. Before opening this issue, I tried to get help on Discord here: https://discord.com/channels/449251231706251264/1431034742458941480/1431034742458941480. When I didn't get a response, I created this issue.
In my example I have a single state property name whose initial value is "World". I have a hello-world web component that renders "Hello, {name}!". I have a labeled-input web component that renders a label and an input element whose value is the state name property. When the user changes the input value, the state name property is updated, which triggers the hello-world component to update. I have "Reset" button that sets the state name property back to "Hello", which triggers the two web components to update.
I could certainly implement this without using some kind of state object. But my goal is to show how each web component library supports sharing state between components that are not necessary near each other in the DOM tree.
You said that FASTElement supports two way data flow. That sounds like what I should use in my example and is something that wrec also supports. Is there a web page that documents that or an example of using it that I can examine?
I'm trying hard to accurately describe the capabilities of vanilla, Lit, Stencil, FAST, Solid, and wrec web components. I don't want to short-change FAST by failing to describe it accurately.
@mvolkmann we’ll try and get a more formed response to you as soon as we can. There are technically a variety of ways this can be accomplished, both with FAST and by leveraging other OSS state management libraries as well. Some are currently documented far better than others. Appreciate the patience!
Hey @mvolkmann I spent some time this week to work up some examples. There's a Redux-like context provider and a Signals'ish global state example. <spoiler-alert> I'm going to reccommend the Signals'ish approach. </spoiler-alert>
Redux-like context provider with @microsoft/fast-element/context.js
https://codepen.io/davatron5000/pen/QwyVoxW
This is a classic nested element structure where state gets pushed up to the parent ancestor that has that manages the state object. I made some notes in that demo on how it works but... it's a lot. A lot of self-management and custom handling to make it reactive which (without documentation) wasn't easy to set up and get working correctly if I'm being honest. You need to setup a provider, add handlers, register subscribers, notify subscribers, callbacks... it's not a very linear process.
A little more API info here: https://fast.design/docs/api/fast-element/context/fast-element
Signals'ish state with @microsoft/fast-element/state.js
https://codepen.io/davatron5000/pen/JoGmOey
@chrisdholt alerted me to an undocumented beta state() API hiding inside FASTElement. It took about a minute to get up and running after reading throught the tests. This is my recommendation. You create some state globally... then update that directly and it's automatically reactive without any extra effort.
At the global level...
const myState = state("hi")
Then in your component...
myState.set("goodbye")
Then in your template...
const template = html`<div>${() => myState.current}</div>`
It can also do "computed states" which I demo'd in the CodePen.
-
state.js - Has the source for
state(),ownedState(), andcomputedState() - state.spec.js - Tests that give examples of usage.
🔑 Key takeaways for me...
Like both Chris and I said above, there are a lot of ways to manage state in FAST. Even ways I didn't know about! 😅 While it's confusing (because we lack docs), I have a renewed appreciation for the flexibility that web components –and particularly FASTElement– offer on the different ways to handle application state.
Thanks @davatron5000! I'll try out these approaches and hopefully include at least one of them in my book. I didn't close this issue yet because I want to provide feedback here soon.
state.js is exactly what I was hoping to find! My demo app can be found at https://github.com/mvolkmann/fast-with-state. This is what I plan to include in the book. I'd love to get feedback on whether there is anything I can improve, especially if I've done anything that isn't considered to be the idiomatic way in FAST, including variable naming.
Overall, looks great. I think the only "idiom" you're breaking is maybe the functions inside the html tagged-template-literal.
You're using ${(e) => e.getName() } when in most of our docs it's written as ${(x, c) => x.getName()} which might cause confusion for your readers later on. I was confused by this as well when I moved from Lit to FAST...
-
x= element context - the first param contains element instance information, used likethisbut in the template -
c= execution context - a second param where you can access parent and event data -
e= from your example - you're using this for "element" which is smart, but that usually meanseventwhen dealing with events, so might be confusing.
This is by no means a hard-and-fast rule, sometimes I write it as ${(el) => el.getName()} so I don't get confused.
One thing I'll add is that properties which are camel cased and are @attr can (and IMO should) have a corresponding attribute that follows HTML syntax. It's not a bug, nor is it something that we throw on, it's just "best practice" IMO. https://fast.design/docs/getting-started/fast-element has an example of this just below the fold.
Thanks @davatron5000! I changed all the "e" parameters to "x".
@chrisdholt I was under the impression that camelCase properties are automatically supported as kebab-case attributes. For example, my labeled-input.ts file contains @attr inputId: string;. In my my-app.ts file I have this in the template:
<labeled-input input-id="name" label="Name" name="name"></labeled-input>
Note how it includes the attribute input-id. I never specified that it maps to the property name inputId. I think you are suggesting that I should change the attribute declaration to this:
@attr({
attribute: "input-id",
mode: "string"
}) inputId: string;
Does FAST figure out that mapping automatically without that change?