Daniel Stokes

Simple State Store for StencilJS

November 18th 2019 3:06pm
Updated: November 18th 2019 3:58pm

UPDATE: When I initially wrote this post I wasn't completely confident in this pattern. I have since come across another post regarding the same topic that you might be interested in. https://www.joshmorony.com/using-services-providers-to-share-data-in-a-stencil-js-application/

Josh's explanation is a little more succinct and comprehensive so check that out if your feeling apprehensive like I was.

Overview

I will quickly show you how to setup a basic "store" for stencil components so you can access data without needing to nest it through properties.

The Problem

As an example I will outline a problem you might encounter when developing with stencil components:

The application requires some access token to interact with some 3rd party api.

You perform the api access request and receive an access token.

You have created a search component that makes a get request to the api but requires the access token.

You have another component that stores a list of saved search result items with a save button that makes a post request to the api. The save post request requires the access token.

Without a store component you would have to:

  • Wrap all components under one so that the data scope is shared.
  • Pass the data down through the node tree as repeated properties.
    Ex:

/components/app-component/app-component.tsx

	import { Component, Prop, h } from '@stencil/core';


@Component({
  tag: 'app-component',
  styleUrl: 'dsappcomponent.css',
  shadow: true,

})
export class AppComponent {

	accessToken: string = "abcde12345";

 	 render() {
   		 return 	`<div>
					<search-component accessToken={this.accessToken}></search-component>
					<list-component accessToken={this.accessToken}>
						<save-component accessToken={this.accessToken}></save-component>
					</list-component>
				</div>`;
	}
}
	

This approach gets more complicated the more components you have and the more data you have.

In other front end component frameworks like vue, angular or react you would have an app component which wraps all your components, the beauty of stencil is that this wrapping is not required; stencil components can be used anywhere. The other frameworks offer a feature called stores or state tunnels or contexts.

You might think that you could just create a variable and attach it to window or document so that you know that it is reachable but typescript will throw an error for that. Even if you can ignore that error it would be better to setup the store so that it is documented in some code more accessibly.

Solution

To setup a simple store for stencil components we will create a new State class. The state class will contain any data or methods that we would like access to.

/utils/state.ts

	class MyState{
	
	accessToken: string;
}

export const State = new MyState();
	

Now any component can import the State class

import {State} from '../../utils/state';

and read and write the required variable.

State.accessToken


This is a very basic example but it demonstrates how this functionality can be accomplished without any additional packages. It is completely up to you how you want to extend this.

If you are using some sort of server-side rendering/processing like blade, handlebars, ejs, twig ect and you want to initialize your state object with data from the server then you could create a state-component and expose properties for the data you want to receive from the server.

/components/state-component/state-component.tsx

	import { Component, Prop, h } from '@stencil/core';
import {State} from '../../utils/state';

@Component({
	tag: 'state-component',
	styleUrl: 'state-component.css',
	shadow: true,
})
export class Card {

 	@Prop() accessToken: string;

	componentWillLoad(){
		State.accessToken = this.accessToken;
	}
}
	

and then in your html/twig/blade/handlebars/ejs you can insert your server variables into these properties on the <state-component> node

ex:

template.ejs

	<body>
	<state-component accessToken="<%user.accessToken%>"></state-component>
</body>
	

Comments