Video summary
Kevin Kucharczyk begins his presentation by addressing the challenge of maintaining consistent component APIs across both React and Ember frameworks within a unified design system. While he initially aimed to use React Context in his library, he realized this pattern was not natively available in Ember, forcing him to explore alternative state-sharing mechanisms like prop drilling, contextual components using `yield`, and global services. He explains that while these existing patterns are useful, they do not fully solve the problem of deep nesting where a grandchild component needs access to data provided at the top level without passing it through every intermediate layer. To bridge this gap between frameworks, he developed an Ember add-on called "Context" which introduces two new decorators: `provide` for injecting state into a specific component tree and `consume` for retrieving that state within nested components or template-only scenarios.
The core of the talk demonstrates how these Context tools simplify complex UI patterns by eliminating verbose prop drilling and enabling more composable, headless libraries. Kucharczyk illustrates this with examples from form fields and accessibility attributes; instead of manually passing IDs and linking labels via `for` and `aria-describedby` attributes through multiple layers, a parent component can provide a context containing these values automatically. This allows child components to simply consume the necessary data without needing to know about internal implementation details or worrying about whether specific helper components are rendered. Furthermore, he shows how this approach facilitates functional patterns similar to React hooks, allowing developers to create reusable functions like `useSelect` that directly access state from a parent context, thereby reducing boilerplate code and improving developer experience when building complex form controls.
However, Kucharczyk also highlights the importance of understanding Ember's reactivity model when implementing Context, as improper usage can lead to infinite rendering loops if tracked properties are not managed carefully within getters used for `provide`. He shares a debugging story where an accidental circular dependency caused his application to crash with a maximum call stack error. The solution involves either splitting large contexts into smaller, named ones or providing stable references like class instances rather than reactive getters that recompute on every change. Despite these nuances, he emphasizes that Context is simply another tool in the developer's toolbox and not an objective replacement for all other patterns; developers should still utilize contextual components when shipping tightly coupled parent-child pairs or rely on global services for app-wide concerns like authentication to choose the most appropriate pattern for their specific use case.
In conclusion, Kucharczyk invites the community to adopt this new Context add-on in their projects and encourages them to share their own experiences with it via Discord. He notes that while an RFC exists to potentially integrate these features directly into Ember core, his immediate goal was to provide a practical solution for developers who want React-like composability without compromising on framework consistency. By offering helpers to inject contexts easily during testing without modifying templates, he aims to lower the barrier to entry and make state management in deep component trees more intuitive. Ultimately, the talk serves as both an introduction to how Context works in Ember today and a call for community collaboration to refine these patterns further before they might become part of the core framework in future updates.
Read the full video transcript
thank you for the introduction um sorry
to disappoint you I do not have an
Australian accent because I am
originally from Poland or kind of from
Germany the the accent is all over the
place I currently live in Australia so
this talk was initially supposed to be a
little bit bit of a surprise sort of
thing like oh what if Ember had contexts
but the surprise has been spoiled by all
the previous talks that have mentioned
context already um so you know it
already exists um but maybe not everyone
knows what context is all about um some
of you maybe have used react or spell or
other Frameworks that already have
context um so I'll introduce you to the
cont concept first so don't worry we'll
learn all together um but first so
custom myio our main product is still an
ember app but recently I found myself
working with react projects as well and
we're updating our company's design
system and the component library that
comes with it uh this is one of our
testing screenshot with all the button
States um due to the nature of our
projects though we decided to build two
component libraries at the same time so
we have a separate library for react and
Ember but they're both based on the same
design system and the same
tokens um but yeah whether that was a
good idea at all um building two systems
not sure if that was good uh but I'm not
going to dive into that cuz that would
be an entirely different talk like how
to build two systems at the same time uh
not today um but I found myself building
these components in parallel for both
Frameworks at the same time we also have
a Rec cool story book set up that
compares the components to each other
again not today um but my aim was to
keep the component apis as similar as
possible because I wanted our developers
to um still know what's happening and
how to use the components when they
switch between projects
um and on the react side I used context
to provide state to the components uh to
the library
components uh but then I realized that
context is not available in Ember so I
couldn't quite make the apis that
similar because um obviously the
internals would change you'd have to
change the way how you invoke the
components so I had to rethink things a
little bit a little bit but before I
dive into that uh let's talk about
context itself first so by the nature of
HTML the websites and apps that we build
uh they're always these tree like
structures HTML elements are nested in
each other and they create branches that
can be many layers
deep and components in all the
Frameworks that we use uh building on
top of HTML they are also rendered in
trees uh in a components template we can
invoke simple HTML or we can invoke more
components um
building them to any sort of depth that
we want and with components uh we can
also pass down arguments deep down the
tree the components also form
relationships we can talk about parent
components or child components referring
to the components that are directly
above or below uh or we can talk about
ancestors and descendants talking about
components that are um all the um
components further up the tree or all
the children and grandchildren of a
particular component
um and when we render these components
we pass arguments into them which could
be like State uh call backs options
whatever we want and then as the
complexity of complexity of our app
grows the component tree can become
quite deep uh we have components that
render children and grandchildren and so
on and sometimes the great grandchild
component will need access to a variable
that was passed in at the top level and
the only way to get that variable down
there is to pass it through each layer
we have to explicitly pass that prop or
argument through each component in that
tree until it reaches the component that
needs it and this repeating this passing
of arguments is something we usually
call prop Drilling and there's nothing
really wrong with prop drilling as such
it's something we have to do um but it
can become tedious having to repeat
arguments through all these levels
sometimes
uh and can also make our components a
bit harder to maintain if we have to
repeat arguments that sometimes are just
not even relevant to one of the
components it's just the component is
somewhere along the way uh and it needs
to pass this argument through it's it's
just
unnecessary and context is a way to
solve that prop drilling problem in like
a dependency injection sort of way um a
context can be provided we say at a top
level and then any component that
renders as a child or grand child like
somewhere in that tree deeper down
within the provider can access that
state like similar to Services where you
just get this state almost out of
nowhere um and if you work with react or
view or spelt you probably used this
already in react this is called create
context and use context view has provide
an inject and I now realized the links
are a little bit cut off um so I'll
share these slides later um so we have
provided inject in view we have uh set
context and get context in
spelt and to put this into practice this
is what it would look like in
react you create a context object which
itself doesn't do much it's just a
representation of the state that you
will be making available but it gives
you a provider component which you can
then render in your app and that
provider component receives a value and
that's the value that will be made
available to all the children and
grandchildren
and then if you want to use that context
retrieve the state in a component that's
deeper down tree you call use context
and give it that context representation
that you created earlier and now you can
pull out the data that you've provided
somewhere further up the tree and
anytime the data changes this component
will also reender so it subscribes it to
any sort of reactive
State um so this is context it's quite
simple it looks like service injections
it's great um and if this is we'll have
some em based examples along the way so
don't
worry so going back to my design system
while the react components used context
and they work quite well I couldn't
build that same thing in Ember so I had
to re-evaluate what I can do first
before making any further decision so I
stopped my development and I thought
about what is possible to Ember right
now and how that might apply to react
because maybe I can just like Drop
context and use amember patterns in
react um the spoiler is that I didn't um
but I did have to reevaluate what is
available so I broke it down to three
state sharing patterns essentially um we
can pass data around with prop drilling
which we've covered um Ember also has
contextual components and services of
course um which we'll dive into in a sec
with prop drilling um if you were
building component library or using a
component Library you might agree that
um if the library didn't have context
didn't have context components it would
be pretty annoying tedious having to
pass around Arguments for the library
like you can imagine a select or drop
down menu component where the drop- down
menu items uh need all the same
arguments repeated that you've already
passed into the select component it's
super annoying super tedious so you
don't want to write your component
libraries this way which is where um
other sharing uh State sharing patterns
come in like contextual components you
might already be thinking well I've been
talking about prop drilling uh
contextual components already solve that
problem yeah they Doo if you're not
familiar with contextual
components um this is a quick reminder
contextual components is when we uh
yield use a yield keyword to um make
certain components available as you
consume another one so you combine the
yield keyword with the component helper
to uh provide the develop with
components with partially curried
arguments so that you don't have to
manually pass those arguments in it's
already like a preconfigured component
basically so here the select components
which will be an example that we'll use
for a little bit in this talk could
yield the child components like a label
or an item with partially applied
arguments already so the developer
that's using this component doesn't have
to repeat values that are internal and
Tides into the component like a selected
value you don't have to think about that
when consuming a component which is
great this is a wellestablished widely
used pattern in Ember you're probably
familiar with it and I'm a big fan of
the composition patterns that this makes
available like this is so unique and
really cool about Ember uh this doesn't
really exist in other compon other
Frameworks this is one of my favorite
features like the composition patterns
just fantastic but it doesn't really uh
fix prop drilling it doesn't remove prop
dring it just moved the prop drolling
elsewhere it it's still there if the
yielded components the select items
component um had more components deeper
down the tree you'd still have to repeat
all these props it doesn't actually
solve the whole
problem and then we also have services
and services are probably the closest
thing we have to context right now in
Ember uh but they don't really ex um
address the same problem services are
Global you usually have you can have
single um you can have multiple
instances of services but you don't
usually do that so a service is usually
a Singleton instance Global throughout
the app any component accessing a
service would be accessing the same
value so if you want to restrict the
state that you're passing to a component
tree um that's not going to do it this
is global um it's not going to help
solve your
issues so before I continued building my
components I did some thought
experiments and you know where these
thought experiments ended up cuz you
already know that the add-on exists so
this was supposed to be the sort of
hypothetical what if scenario um but
that's what I did so I started thinking
about what if we did have context and I
wrote pseudo code at this time when I
was writing this down like this didn't
exist I was just thinking what if so I
thought that if there was context um
like I said services are already quite
similar to how I feel you might consumer
context if we did have context I imagine
we might have a decorator that I called
consume you give the consume decorator a
context name just like you give the
service decorator a service name and it
just Returns the value of um the context
simple additionally U I feel like a
context consumer component might be
useful for usage in templates or
template only components where you don't
have a backing class to apply The
Decorator to so there a context context
consumer component where you give it the
key which is again the context name and
it yields the context value which you
can then use in your template um I think
this was shown in one of the previous
slides the empata upgrade path so this
is not entirely unfamiliar to you
now but then how do we provide context I
couldn't quite use the service patterns
that we have available because again
services are uh installed globally but
context needs to be tied to the
component trees it needs to live on the
component level so I couldn't create a
CL class that's um initialized and
registered in the app so I started with
The Decorator again because that's a
pattern that we're familiar with we're
comfortable with decorators uh and this
one is called provide it's kind of
similar to the um view provide function
just naming you give it the context name
which is the one that we use in the
consume decorator and you attach it to
anything you want really here it's
attached to a getter and whatever the
getter returns will be the value of to
context you could also attach it to a
stable reference or a a property or a
class instance that you initialize it
can be
anything additionally uh there's a
context provider component similar to
the context consumer again to use your
context in template only
scenarios so from a developers
perspective you don't need anything else
to use context this is it this is uh two
decorators two components uh you're
ready to go
so you know it took me a little bit of
talking to just introduce two boring
decorators to components uh that could
be it you know I could say thank you get
of stage but example is where context
really starts to shine like we need to
see it in action so uh let's have a look
at some of those examples I am going to
back to the select component that I
mentioned before uh and I have to
reiterate that contextual components
work quite well for a select component
you can see this in Ember Power select I
feel like that's one of the most popular
add-ons in the the community um and yeah
there's nothing wrong with using
contextual components for this but if we
use context for this one I feel like the
components API could simplify a little
bit further or be a little bit more
composable so the top one is the
contextual version and the bottom one is
the context version it's terrible naming
bottom one is the context version um and
in the new version the parents component
would no longer have to yield any item
components or Curry any arguments into
them all it would have to do which is
not pictured here uh that's an
implementation detail it would only have
to provide a context to be used for any
components that are nested within um the
code we would have to write as a
developer to use the select component
changes a little bit we no longer have
to write as s to get access to whatever
has been yielded um because the yield
statements have been removed anything um
that's R within component that that's it
there there's nothing yielded we've also
replaced the yielded label with a
generic form field label component which
we could then reuse across any sort of
form field component U no matter where
we use it because it uses context it
would access the value with the consumed
decorator no matter where it's put
within the select component it would
always have access to those values um so
it could be universally used across many
other components and then finally the
select item component we no longer use
any yielded once so we just invoke a
regular component by its name so when
you put these examples side by side it
just highlights how similar these codes
snippits are like what's the point um
the developer that's using uh these new
components maybe doesn't see a benefit
immediately but the developer who builds
the component the select component like
a component Library developer or an M
Power select developer can see a much
larger benefit um say the select
component lived in an add-on that um is
like a component Library add-on uh many
reusable components and you'd like to
allow other developers to use these
select components but also style them to
match your your business and styling
needs this uh library is called headless
libraries referring to the fact that
they don't have any Styles or like very
minimal styles that are easy to override
um and that's a common scenario so if we
were trying to customize an addons
contextual component that doesn't have
any Styles or mineral Styles we may not
have access to any of the internals to
actually configure these things
comfortably to apply our Styles we would
have to wrap the select components and
yield our own item components carrying
all the same arguments that the original
author already carried so we'd have to
look at the original libraries code and
basically duplicate the templates but
with our own components um I've seen
this a little bit in our app with Ember
Power select where we have to look into
the Ember Power select internals and
copy over all the arguments so that we
can customize the component it's
possible but
tedious so then for the select item
component we'd have to do the same here
we apply our own uh classes could be
tailor classes whatever styling system
we use uh but then again we'd have to
copy over all these arguments that
already are defined in some other
Library it's it's a very annoying
process and whenever anything changes in
the other Library you have to copy off
the arguments again
it's a lot going on so if to add-on used
context to provide the state a developer
like me would have a much easy time
trying to customize these components uh
because the components are no longer as
connected I can style my components in
isolation knowing that the context that
provides the value is accessible no
matter where the components are so I can
style my components in isolation I can
rely on the fact that the select item
component the one that actually does the
work in ter
will have access to the value no many no
matter how many wrapper components I
will need to add to achieve my styling
needs and in fact you may not even have
to use a select item component at all
context would allow you to build things
like things kind kind of like react
hooks here uh you can imagine we could
build a hook uh or function that's
called use select which does basically
the same thing that the select item
component would do it gets access to the
select select parent components context
and gets all the values out of it and
does certain computations that um you
can then um use within your component to
access all the state like you can build
your select item component um making
sure knowing that we have access to the
context and here the use select function
you can see that it doesn't have the uh
provide or consume decorator attached
like in the previous examples and this
is a pattern that we could introduced
that is similar to the use function from
mro sources or the uh hypothetical
service the functional form of service
injection from Ember Polaris service uh
so it's again a pattern that is already
existing in Ember and we can do this
because if we pass in this into our use
select function the um use select
function has access to the component
instance and through that instance it
has access to the owner the Ember app so
we can hook in that function into
whatever does the context tree internal
tracking so if you've worked with react
hooks uh this kind of looks like react
Hooks and uh since we're talking about
Forum elements let's look at some other
Forum element examples uh we had a
accessibility talk earlier today so
accessibility is a good example to talk
about um one common accessibility
requirement is to provide labels for
your phone controls and your phone
controls could also have additional help
text or error messages applied and to
make these accessible uh you usually use
additional attributes to connect all
these together here the um label points
to the input via the for attribute which
points to the input's ID and then you
have the description which is linked
with the input via the area described by
attribute which points to the
paragraph's
ID so if we used context we could build
components that generate these
attributes and then later set them
automatically for us so when we're
building our app we don't actually have
to think about any of the accessibility
details it all just hooks it up
automatically for us so we're building
a yeah I hope you can see this we're
building a hypothetical generic form
field component which we can then use as
the parent component for any sort of
form field like text input or select uh
box and there's a few things going on
here first we generate a unique ID for
the whole group of elements if one isn't
provided as an argument already we
generate unique IDs for any elements
that might exist within that component
like the description or the input itself
and we also create a modifier here which
uh will then register the existence of a
description to know whether a
description has been rendered at all and
you'll see in the next slide how this
works and finally we take all these
values and provide them in a context so
that all the child and grandchild
components can actually access the these
values then we have the form field child
components which might look like this I
tried keeping it simple but it's very
hard to fit all this code um so the
individual form components they would
consume the form field context and then
set the necessary attributes and the
description component it attaches the
modifier that we made available in the
form field which then lets the Forum
field context know that the description
has been rendered which the input then
uses to render the area describe by
attribute so then when we consume these
components uh we don't have to worry
about any of these internals we just
render it like this and because the form
label the text input everything is
nested within form field all these uh
context things are doing the setup for
us this is already accessible you don't
have to do anything extra you could pass
in an ID if you'd like uh if you don't
have to it's all done internally so as a
developer I can compose these components
in whatever order shape I want I can add
any um wrappers which I might need for
styling needs I can rearrange them and
they always work the same if then nested
within the form field so using context
we could build libraries that make it
even easier to do accessible stuff like
we could have utilities and components
that abstract this these things even
further but then you run the code that I
just wrote and it it it actually doesn't
work um you get this maximum call stack
size exceeded error um and yeah maybe
you just shouldn't work with context at
all I'm not going to ship this to
production uh I don't think I can write
good code at all
and I think I saw a few skeptical faces
in the audience a few slides back so
let's go back to the form field and see
what went
wrong and this is just to highlight that
anything you provide in context is just
like regular reactive Ember code um so
the problem that I'm seeing here is
actually not unique to context at all
this is just me not being careful about
my ghs and my track properties so what
happens here is uh the form field
context is a getter which means that it
will always recompute reender when any
of the track prop references change uh
you can see that the register
description modifier which we've
attached to the description component uh
it sets the H description property uh
which will have the form field context
recompute which will have the
description component reun the modifier
which again will set the H description
and it'll just keep running in this Loop
that's why it
breaks um and again this can happen
easily um even without context if we're
not careful about our gets um but for me
it's it's particularly important with
context to be very Vigilant about how we
arrange our GS and our track properties
because I feel like the the
disconnection that exists between the
components that provide and consume
context just makes it easier to forget
that these connections actually do exist
so we have to be really careful about
how we arrange our po
properties but how do we fix this
particular issue well one thing could be
to break it up into multiple smaller
contexts one component can provide
multiple contexts as long as they all
have different
names um but of course we still run the
risk of creating infinite renders here
uh the actual problem here is fixed by
removing the H description accessor from
the description context that has been
moved into the form control
context uh and by the way as your app
grows and you may have seen this in
react context implementations if you
have a lot of template based context
your app is just going to keep growing
sideways and there's nothing wrong with
that like the app will still render just
the same but if you're trying to debug
this in EM insector you're going to have
to scroll like very far to get to your
actual apps content to try and like
debug anything and with this
implementation of context by using the
provide decorator you can provide
multiple context from one component so
you might not see this issue at all
which is great for Ember bad for
react so back to the form now after we
split up the big context into many
smaller context the individual form
components look like this the admittedly
quite similar they just access their own
um small individual contexts now um but
in this refactor there's one additional
benefit Beyond just fixing the bug
previously I hope you noticed that the
text input component was checking
whether the description is rendered and
then setting the area described by
attribute which means that it has to
know that a description component is a
thing and that may render but now all it
receives is the area described by
attribute directly it doesn't have to
worry about what components do or do not
exist uh all it receives is what value
do I set for what property it doesn't
have to worry about any externals that
are happening and this now renders this
actually works great um there's
alternative fixes um and I won't be
diving into the details here but
essentially what you have to do is uh
remember that anything can recompute so
if you wanted to fix these recompute
issues you can also provide a stable
reference to your context you can
provide a class instance or you can
provide a
simple object that then includes smaller
gets that recompute whenever a specific
property changes and not when any
property
changes there's more use case for
context Beyond just forms um you could
provide form validation where your top
level form component has a validation
context which carries all the necessary
uh State and functions to compute your
validation error messages and hooks them
into your um you know text inputs or
select inputs automatically so you don't
have to wor about passing through all
that state um and there's many other use
cases for this that I sadly don't have
time to dive into today but I'd love to
talk to you all about it later if you
have the time um but of course context
isn't always the solution to all our
problems context it's just another tool
in our toolbox to build really cool
composable components it's not
objectively better or worse than the
other patterns it's just another thing
we can use so you might still use
contextual components if you want to uh
ship your parent and child components
together like if you have very uh
particular usage patterns you can still
ship those components together you don't
have to use context if you know the uh
if you want to deliberately restrict
composition if your components only work
a particular way you maybe you don't
need context U just ship them as one
thing um and also if you know that
there's no deep nesting if there's a
very straight parent child relationship
you can ship them as contextual
components you know there's not going to
be any extra layers so just go for it uh
and services of course will always be
great for Global any sort of global uh
information you can use it for
authentication API handling logging
whatever you can use context for that as
well but typically you don't need to
override things like um API handlers uh
for particular component trees usually
that'll still be fine as a global thing
if you have a use case for um logging
certain things for a particular
component tree go for it context is
great for
that and finally how do you test with
context so usually you would like to
test your components in the same sort of
environment that will run in in uh
production so you want to run in under
realistic scenarios which means that if
you have something that provides the
context that's what you want to use in
your test if there's a component library
that gives you the provider component
you want to invoke that provider
component in your test and wrap all your
tests in that component so if you have a
lot of integration tests for your
component that consumes this provider
you'd have to repeat this provider uh
component in all your tests which might
become tedious like if you have hundreds
of these you always have to repeat the
provider which you don't want to do so
we could introduce a helper which I
would love to see added in empit test
helpers which uh maybe you should open
up request for that but we could add a
helper that allows you to override the
test template for a group of tests um so
you define that in your before each hook
to override the test template for the
whole module and then this uh will under
the add-on provider outside of your
component for all the tests in here so
all the tests will use the provider
already you can also use the context
provider if you want more control over
the particular values that you want to
provide but if all you're trying to do
is to overwrite the value for a
particular context we can also introduce
a helper that uh does just that without
modifying any sort of template stuff we
have a helper that's called provide
which basically injects the context
straight into the test so there's no
extra template stuff happening uh you
can this way give it the values that you
want to use and I believe empid is
already using this sort of thing in
their tests or was trying to uh and this
way you can also Pro provide multiple
different contexts in your tests without
having to do the whole nesting thing in
your
templates um and this was supposed to be
a surprise um hey you can use context
already that's an add-on that I wrote I
didn't want to compromise in I uh my
component Library I didn't want the apis
to be different between react and Ember
um and I didn't want to give up on
context because I I really enjoyed the
developer experience in react that
context enabled the composability that
was available so I went ahead and built
an add-on that does it for Ember um
which you can use these days and I've
heard a couple of people are already be
using that in their projects but of
course there's also the context RFC
which you may or may not have seen I
believe context should be part of Ember
and we're working on making that happen
context based components already live in
our production UI um that's been
shipping for a couple months now and I'd
like to invite all of you to try context
in your add-ons or apps or test products
and please talk to me about how you've
used context in your apps tell me about
your use cases um I'll be here now you
can find me on Discord um and that's it
thank you for listening and let's chat