CWCO logo

v1.7.6

Context

One of the most powerful CWCO features is the built-in support for context which allows you to pass data deep into child components and divide your data into sections for your app.

What for?

  • It is great for setting global data for features like theming and internationalization as well as defining what data should be available everywhere in the app like user login information for example.
  • It is also ideal for when you want to define data for a specific part of the app that the rest of the app does not need to know about. You can create components which sole job is to manage a specific data and then reuse it everywhere replacing only its children.
  • Ideal for data store and management.

initialContext

You can specify your initial context data which is great for the first render of the app if your template depends on it.

class TodoApp extends WebComponent {
	static initialContext = {
		lang: 'en-US',
		theme: localStorage.getItem('theme') ?? 'dark',
		todos: [],
		loading: true,
		errorMessage: '',
	}
}

TodoApp.register();

If you come from the world of React and Redux, this is equivalent to initialState.

updateContext

The updateContext is the only method you need to know about when it comes to managing context data. It does both, define and update context of the component.

class TodoApp extends WebComponent {
	static initialContext = {
		lang: 'en-US',
		theme: localStorage.getItem('theme') ?? 'dark',
		todos: [],
		loading: true,
		errorMessage: '',
	}
	
	onMount() {
		fetch('/api/todos')
			.then(res => res.json())
			.then(res => {
				this.updateContext({
					todos: res.data,
					loading: false,
				})
			})
			.catch(e => {
				console.error(e);
				this.updateContext({
					loading: false,
					errorMessage: e.message,
				})
			})
	}
	
	get template() {
		return `
			<h2>Todo App</h2>
			<todo-list></todo-list>
		`
	}
}

TodoApp.register();

The updateContext only updates the properties that you provide, but it does not do a deep update. The example above will not change the lang and theme properties when updating the todos, loading and errorMessage values.

Whenever you call updateContext the DOM updates as well as any descendent element. This happens regardless if you update the context with the same data. It does not do any checks whether the data has changed or not. Because of this, you should only call it when you have a change.

$context

WebComponent exposes your defined context through the $context property and every component has one. It is a read-only property so to make updates you go through updateContext.

class TodoList extends WebComponent {
	onMount() {
		console.log(this.$context);
	}
	
	get template() {
		return `
			<div class="todo-list">
				<todo-item repeat="$context.todos" name="{$item.name}" status="{$item.status}" description="{$item.description}"></todo-item>
			</div>
		`;
	}
}

TodoList.register();

The onUpdate will be triggered after a context change which is a safe place to react and do anything you need.

You can access the context in the template as well. This is particularly great for global data not specific to the component

class TodoItem extends WebComponent {
	static observedAttributes = ['name', 'status', 'description'];
	
	get template() {
		return `
			<div class="todo-item {$context.theme || 'dark'}">
				<h3>{name}</h3>
				<p>{description}</p>
				<p><strong>Status:</strong> {status || 'open'}</p>
			</div>
		`
	}
}

TodoItem.register();

The TodoItem has a loose dependency on the theme which is set in the app. This is okay as TodoItem is a very specific component to the place it must be used so the dependency is fine. In general avoid having generic components depending on the context of the app unless you know they will be used with a specific context provider.

Accessing Context

The $context can be used to read context from anywhere including from outside the component. From the template you don't need to reference $context, you can simply grab the property directly and in doing so you need to know that if there are any class property or attribute with the same name they will override the context property with the same name.

The example below reads the searchTerm directly instead of referencing $context. If the form had a searchTerm attribute it would grab the attribute instead and, you might have to reference context directly to make the difference.

class SearchForm extends WebComponent {
	static initialContext = {
		searchTerm: '',
	};
	
	get template() {
		return `
			<form onsubmit="handleSubmit()">
				<input value="{searchTerm}" oninput="onInput($event)"></input><//input>
				<button>search</button>
			</form>
		`
	}
	
	onInput(event) {
		this.updateContext({
			searchTerm: event.target.value
		})
	}
	
	handleSubmit() {
		// submit logic
	}
}


SearchForm.register();

For cases like the above it is best to use class properties as context is too robust for such simple things.

Context Provider

You can combine the power of template slot and context to create context provider components.

class ThemeProvider extends ContextProviderComponent {
	static initialContext = {
		theme: 'dark',
		primaryColor: '#222',
		secondaryColor: '#ddd',
		ctaColor: '#930',
	};
}

So whenever you need to use this information you can simply wrap the part of the app with this component.

<theme-provider>
	<button type="button" style="background: {primaryColor}; color: #fff">themed button</button>
</theme-provider>

Check the ContextProviderComponent doc for more details.