Advanced Components
As your project gets bigger, the way you manage components and data may need to grow with it. Reef has some options to help make things a little easier.
HTML Templates
Default and state-based HTML attributes
You can use component data to conditionally include or change the value of HTML attributes in your template.
Use the reef-checked
, reef-selected
, and reef-value
attributes to dynamically control the checked
, selected
, and value
attributes, respectively. Use a falsy value when the item should not be checked or selected.
In the example below, the checkbox is checked
when agreeToTOS
is true
.
let app = new Reef('#app', {
data: {
agreeToTOS: true
},
template: function (props) {
return `
<label>
<input type="checkbox" reef-checked="${agreeToTOS}">
</label>`;
}
});
You might also want to use a default value when an element initially renders, but defer to any changes the user makes after that.
You can do that with the reef-default-checked
, reef-default-selected
, and reef-default-value
attributes.
In this example, Hermione
has the [selected]
attribute on it when first rendered, but will defer to whatever changes the user makes when diffing and updating the UI.
let app = new Reef('#app', {
template: function () {
return `
<label for="wizards">Who is the best wizard?</label>
<select>
<option>Harry</option>
<option reef-default-selected>Hermione</option>
<option>Neville</option>
</select>`;
}
});
Try controlling form attributes on CodePen →
Preventing Cross-Site Scripting (XSS) Attacks
To reduce your risk of cross-site scripting (XSS) attacks, Reef automatically encodes any markup in your data before passing it into your template.
You can disable this feature by setting the allowHTML
option to true
.
Important! Do NOT do this with third-party or user-provided data. This exposes you to the risk of cross-site scripting (XSS) attacks.
let app = new Reef('#app', {
data: {
greeting: '<strong>Hello</strong>',
name: 'world'
},
template: function (props) {
return `<h1>${props.greeting}, ${props.name}!</h1>`;
},
allowHTML: true // Do NOT use with third-party/user-supplied data
});
Try allowing HTML in your data on CodePen →
Getting the element the template is being rendered into
An optional second argument is passed into the template()
function: the element the template is being rendered into.
This is particularly handy if you have data attributes on your element that affect what’s rendered into the template.
<div id="app" data-greeting="Hello"></div>
let app = new Reef('#app', {
data: {
name: 'world'
},
template: function (props, elem) {
return `<h1>${elem.getAttribute('data-greeting')}, ${props.name}!</h1>`;
}
});
Try getting the HTML element that the template was rendered into on CodePen →
Nested Components
If you’re managing a bigger app, you may have components nested inside other components.
Reef provides you with a way to attach nested components to their parent components. When the parent component is updated, it will automatically update the UI of its nested components if needed.
Associate a nested component with its parent using the attachTo
key in your options
object. You can provide a component or array of components for a value.
You only need to render the parent component. It’s nested components will render automatically.
// Parent component
let app = new Reef('#app', {
data: {
greeting: 'Hello, world!'
},
template: function (props) {
return `
<h1>${props.greeting}</h1>
<div id="todos"></div>`;
}
});
// Nested component
let todos = new Reef('#todos', {
data: {
todos: ['Swim', 'Climb', 'Jump', 'Play']
},
template: function (props) {
return `
<ul>
${props.todos.map(function (todo) {
return `<li>${todo}</li>`;
}).join('')}
</ul>`;
},
attachTo: app
});
app.render();
Try nested components on CodePen →
Attaching and Detaching Nested Components
You can attach or detach nested components at any time using the attach()
and detach()
methods on the parent component.
Provide an individual component or array of components as an argument.
// Attach components
app.attach(todos);
app.attach([todos]);
// Detach components
app.detach(todos);
app.detach([todos]);
Try attaching nested components on CodePen →
Shared State with Data Stores
A Data Store is a special Reef object that holds reactive data you can share with multiple components.
Any time you update the data in your Data Store, any components that use the data will also be updated, and will render again if there are any UI changes.
Create a Data Store using the new Reef.Store()
constructor.
let store = new Reef.Store({
data: {
heading: 'My Todos',
todos: ['Swim', 'Climb', 'Jump', 'Play']
}
});
To use your Data Store with a component, pass it in with the store
property instead of providing a data
object.
let app = new Reef('#app', {
store: store,
template: function (props) {
return `
<h1>${props.heading}</h1>
<ul>
${props.todos.map(function (todo) {
return `<li>${todo}</li>`;
}).join('')}
</ul>`;
}
});
When using a Data Store, a component will have no data
of its own. All state/data updates must happen by updating the store
.
// Add a todo item
store.data.todos.push('Take a nap... zzzz');
Try creating a Data Store on CodePen →
Setters & Getters
Reef’s reactive data
makes updating your UI as simple updating an object property.
But as your app scales, you may find that keeping track of what’s updating state and causing changes to the UI becomes harder to track and maintain.
Setters and getters provide you with a way to control how data flows in and out of your component.
Setters
Setters are functions that update your component or store data.
Create setters by passing in an object of setter functions with the setters
property in your options
object. The first parameter on a setter function is the store or component data. You can add as many other parameters as you’d like.
let store = new Reef.Store({
data: {
heading: 'My Todos',
todos: ['Swim', 'Climb', 'Jump', 'Play']
},
setters: {
// Add a new todo item to the component
addTodo: function (props, todo) {
props.todos.push(todo);
}
}
});
Use setter functions by calling the do()
method on your component or store. Pass in the name of the setter, along with any required arguments (except for props
).
// Add a new todo item
store.do('addTodo', 'Take a nap... zzzz');
When a component or store has setter functions, they become the only way to update app or store data.
This protects your component or store data from unwanted changes. The data
property always returns an immutable copy.
// This will NOT update the store.data or the UI
store.data.todos.push('Take a nap... zzzz');
Try working with setter functions on CodePen →
Getters
Getters are functions that parse data from your component or store and return a value.
They’re useful if you need to manipulate and retrieve the same data across multiple views of components. Rather than having to import helper functions, you can attach them directly to the component or store.
Create getters by passing in an object of getter functions with the getters
property in your options
object. The first parameter on a getter function is the store or component data. You can add as many other parameters as you’d like.
Support for parameters besides props
requires version 8.2.0
or higher.
let store = new Reef.Store({
data: {
heading: 'My Todos',
todos: ['Swim', 'Climb', 'Jump', 'Play']
},
getters: {
total: function (props) {
return props.todos.length;
}
}
});
Use getter functions by calling the get()
method on your component or store. Pass in the name of getter, along with any required arguments (except for props
).
// Get the number of todo items
store.get('total');
Try working with getter functions on CodePen →
Asynchronous Data
You can use asynchronous data (such as content from an API) in your templates.
Set an initial default value, make your API call, and update the data
property once you get data back. This will automatically trigger a render.
// Create an app
let app = new Reef('#app', {
data: {
articles: []
},
template: function (props) {
// If there are no articles
if (!props.articles.length) {
return `<p>There are no articles.</p>`;
}
// Otherwise, show the articles
return `
<ul>
${props.articles.map(function (article) {
return `<li>
<strong><a href="#">${article.title}.</a></strong>
${article.body}
</li>`;
}).join('')}
</ul>`;
}
});
// Fetch API data
// Then, update the app data
fetch('https://jsonplaceholder.typicode.com/posts').then(function (response) {
return response.json();
}).then(function (data) {
app.data.articles = data;
});
Try create a template from asynchronous data on CodePen →
You might also choose to hard-code a loading message in your markup.
<div id="app">Loading...</div>
Debugging
By default, Reef fails silently. You can put Reef into debug mode to expose helpful error message in the Console tab of your browser’s Developer Tools.
Turn debug mode on or off with the Reef.debug()
method. Pass in true
to turn it on, and false
to turn it off.
// Turns debug mode on
Reef.debug(true);
// Turns debug mode off
Reef.debug(false);
Get Daily Developer Tips
I send out a short email each weekday with code snippets, tools, techniques, and interesting stuff from around the web. Join 11,200+ daily subscribers.