This repository was archived by the owner on Sep 3, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandling-events.html
306 lines (262 loc) · 26.5 KB
/
handling-events.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="description" content="Composi is a JavaScript library for creating component-based interfaces. It uses the virtual dom to make efficient updates to the DOM based on a component's data or state.">
<meta name="keywords" content="javascript, framework, performance, small, fast, UI, programming, code, component, composi, chocolatechipui, chocolatechip-ui, reactive, virtual dom">
<title>Composi - Tutorials</title>
<link rel="stylesheet" href="../css/styles.css">
<link rel="stylesheet" href="../css/styles.css">
<link rel="stylesheet" href="../css/prism-tomorrow.css">
</head>
<body class='tutorial-page'>
<nav>
<ul class='nav--menu'>
<li class='nav--menu__item'>
<a class='nav--menu__item__link' href="../index.html">
<svg id='composi-logo' viewBox="0 0 300 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Composi Logo</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Composi-Logo-Solid" fill="#fff">
<path d="M1.77635684e-15,0 L95,0 L95,38 L209,38 L209,0 L300,0 L300,94 L265,93.8571663 L265,209 L300,209 L300,300 L209,300 L209,265 L95,265 L95,300 L1.77635684e-15,300 L1.77635684e-15,209 L40,209 L40,94 L1.77635684e-15,93.8571663 L1.77635684e-15,0 Z M107,107 L107,192 L192,192 L192,107 L107,107 Z" id="Combined-Shape"></path>
</g>
</g>
</svg>
<span class='logo__link--text'>Composi</span></a>
</li>
<li class='nav--menu__item'>
<a class='nav--menu__item__link' href="../docs/installation.html">Docs</a>
</li>
<li class='nav--menu__item selected'>
<a class='nav--menu__item__link' href="./index.html">Tutorials</a>
</li>
<li class='nav--menu__item'>
<a class='nav--menu__item__link external' target='__blank' href="https://github.com/composor/awesome-composi">Resources
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="14px" height="14px" viewBox="0 0 511.626 511.627" style="enable-background:new 0 0 511.626 511.627;"
xml:space="preserve">
<path fill='#fff' d="M392.857,292.354h-18.274c-2.669,0-4.859,0.855-6.563,2.573c-1.718,1.708-2.573,3.897-2.573,6.563v91.361
c0,12.563-4.47,23.315-13.415,32.262c-8.945,8.945-19.701,13.414-32.264,13.414H82.224c-12.562,0-23.317-4.469-32.264-13.414
c-8.945-8.946-13.417-19.698-13.417-32.262V155.31c0-12.562,4.471-23.313,13.417-32.259c8.947-8.947,19.702-13.418,32.264-13.418
h200.994c2.669,0,4.859-0.859,6.57-2.57c1.711-1.713,2.566-3.9,2.566-6.567V82.221c0-2.662-0.855-4.853-2.566-6.563
c-1.711-1.713-3.901-2.568-6.57-2.568H82.224c-22.648,0-42.016,8.042-58.102,24.125C8.042,113.297,0,132.665,0,155.313v237.542
c0,22.647,8.042,42.018,24.123,58.095c16.086,16.084,35.454,24.13,58.102,24.13h237.543c22.647,0,42.017-8.046,58.101-24.13
c16.085-16.077,24.127-35.447,24.127-58.095v-91.358c0-2.669-0.856-4.859-2.574-6.57
C397.709,293.209,395.519,292.354,392.857,292.354z"/>
<path fill='#fff' d="M506.199,41.971c-3.617-3.617-7.905-5.424-12.85-5.424H347.171c-4.948,0-9.233,1.807-12.847,5.424
c-3.617,3.615-5.428,7.898-5.428,12.847s1.811,9.233,5.428,12.85l50.247,50.248L198.424,304.067
c-1.906,1.903-2.856,4.093-2.856,6.563c0,2.479,0.953,4.668,2.856,6.571l32.548,32.544c1.903,1.903,4.093,2.852,6.567,2.852
s4.665-0.948,6.567-2.852l186.148-186.148l50.251,50.248c3.614,3.617,7.898,5.426,12.847,5.426s9.233-1.809,12.851-5.426
c3.617-3.616,5.424-7.898,5.424-12.847V54.818C511.626,49.866,509.813,45.586,506.199,41.971z"/>
</svg></a>
</li>
</ul>
</nav>
<article class='tutorial__article'>
<section>
<div class='tutorial'>
<h1>Handling Events</h1>
<p class="tutorial__intro">There are two ways to handle events: inline and with the <code>handleEvent</code> interface.</p>
<p>Inline events have been around since the birth of JavaScript, and the <code>handleEvent</code> interface was introduced in 2000. It's supported in browsers all the way back to IE6. Inline events are the easy way to get events working fast. However inline events do not enable delegation. That means that if you create a list that has inline events on its items, and there are hundreds or thousands of items, they will eat up a lot of memory. Inline events should only be used for the simplest use case. The <code>handleEvent</code> interface allows you to setup event delegation. You register an event on the component's base element and then use <code>handleEvent</code> to manage what happens with the event targets. This results in minimal memory use.</p>
<p>It's surprising that after all these years, the vast majority of developers are unaware of how to use <code>handleEvent</code> and what its benefits are. Developers are addicted to adding event listeners to elements. These you callbacks that are the source of serious memory leaks. This is because callbacks create closures that trap the reference to DOM nodes being manipulated. Even if you remove the event listener from the element, its callback's closure lives on, hold the reference to whatever nodes and objects it has. In contrast, you can implement <code>handleEvent</code> as a method of your class component. Then your have access to all your class properties and methods through normal use of the <code>this</code> keyword. Because you implemented <code>handleEvent</code> as a method of your component, there is no callback and no closure. If you decide to remove the event, there is no cleanup necessary and never any memory leaks.</p>
<h2>Inline Events</h2>
<p>The easiest way to implement events is to put the event handler right on the element where you want to capture the interaction. Unlike other libraries that use JSX, there is no need to camel case inline events. Use them just like you would for JavaScript. However, one important thing to bear in mind. Inline the value of an inline event must never be quoted. Instead that value needs to be inclosed in curly braces. Let's make a simple class component and add an inline event to alert its value:</p>
<p data-height="300" data-theme-id="6688" data-slug-hash="qVEXxL" data-default-tab="js,result" data-user="rbiggs" data-embed-version="2" data-pen-title="Composi Tuts - events-1" class="codepen">See the Pen <a href="https://codepen.io/rbiggs/pen/qVEXxL/">Composi Tuts - events-1</a> by Robert Biggs (<a href="https://codepen.io/rbiggs">@rbiggs</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>In our above example we did two things. We create a method to handle the event, <code>announce</code>. And we put an online event handler directingly on the element we expect the user to click, in this case, the <code>h1</code>. Notice that we use the <code>this</code> keyword to access the component method inside the event handler. Now whenever the user clicks, they'll get an alert of the text of the <code>h1</code>. The above example was very simple, we just used the event target to get the text content. If we need to access over parts of the component, it get's more complicated.</p>
<h2>Binding "this"</h2>
<p>We're going to change our Hello example so that the user can enter a different name. For that we'll need to add an input and a button to submit:</p>
<p data-height="300" data-theme-id="6688" data-slug-hash="WXbEJp" data-default-tab="js,result" data-user="rbiggs" data-embed-version="2" data-pen-title="Composi Tuts - events-2" class="codepen">See the Pen <a href="https://codepen.io/rbiggs/pen/WXbEJp/">Composi Tuts - events-2</a> by Robert Biggs (<a href="https://codepen.io/rbiggs">@rbiggs</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>In this example, we've moved the <code>onclick</code> inline event handler to the button. We expect that when the user enters a name in the input and clicks the button, that it will execute the component's <code>announce</code> method. However, this is not working. Why?</p>
<h2>Inline Events and Context</h2>
<p>The above example does not work because the scope of the inline event is the element that was clicked. That means for the inline event the value of <code>this</code> will be the button, not the component. We can verify this by logging the value of <code>this</code> in the <code>announce</code> method:</p>
<pre><code class="language-javascript">announce() {
console.log(this)
const input = this.element.querySelector('input')
const value = input.value
if (value) {
this.setState(value)
}
}</code></pre>
<p>When you click the button, you'll see that the method outputs <code><button>Change Name</button></code> in the browser console. In order to give the inline event the context of the component, we'll need to change our code. There are two ways to do this: using <code>bind(this)</code> on the inline event, or simply enclosing the inline event in an arrow function. First let's look at using <code>bind</code>:</p>
<pre><code class="language-javascript"><button onclick={this.announce.bind(this)}>Change Name</button></code></pre>
<p>Notice how we pass <code>this</code> to the click handler using <code>bind</code>. Doing this solves our problem and our example will now work. The <code>announce</code> method can now access the component state because its reference to <code>this</code>is correct:</p>
<p data-height="300" data-theme-id="6688" data-slug-hash="vWEJQY" data-default-tab="js,result" data-user="rbiggs" data-embed-version="2" data-pen-title="Composi Tuts - events-3" class="codepen">See the Pen <a href="https://codepen.io/rbiggs/pen/vWEJQY/">Composi Tuts - events-3</a> by Robert Biggs (<a href="https://codepen.io/rbiggs">@rbiggs</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<h2>Move Bind to the Constructor</h2>
<p>Some of you may really hate the cluttered look of <code>bind(this)</code> on your inline events. There is annother way to do this, and that is to bind the component method in the constructor. To do this your put the method you want to bind there and assign it the bound version. Below is our example updated with this approach:</p>
<p data-height="300" data-theme-id="6688" data-slug-hash="YEPxBz" data-default-tab="js,result" data-user="rbiggs" data-embed-version="2" data-pen-title="Composi Tuts - events-4" class="codepen">See the Pen <a href="https://codepen.io/rbiggs/pen/YEPxBz/">Composi Tuts - events-4</a> by Robert Biggs (<a href="https://codepen.io/rbiggs">@rbiggs</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>With this change, our example now works and our online event is clean.</p>
<h2>Use Arrow Function for Context</h2>
<p>If you don't like having to bind your methods in the constructor, you could just use an arrow function in the inline event handler. Inside the arrow function you invoke the component method,. Because this happens inside the arrow function, the method has the scope of the component. Here's how you do that:</p>
<pre><code class="language-javascript"><button onclick={() => this.announce()}>Change Name</button></code></pre>
<p>Notice that we changed the event handler so that the arrow function returns <code>this.announce()</code>. And that's it. The example now works as expected. Notice for the <code>onclick</code> for <code>announce</code> that we had to pass in the event object to the arrow function and the method invocation:</p>
<p data-height="300" data-theme-id="6688" data-slug-hash="KywvJy" data-default-tab="js,result" data-user="rbiggs" data-embed-version="2" data-pen-title="Composi Tuts - events-5" class="codepen">See the Pen <a href="https://codepen.io/rbiggs/pen/KywvJy/">Composi Tuts - events-5</a> by Robert Biggs (<a href="https://codepen.io/rbiggs">@rbiggs</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p> </p>
<h2>Summary</h2>
<p>When using inline events you have three ways of getting the component scope. This is purely user preference. There is no difference in result or unexpected gotchas with any of the three.</p>
<h2>handleEvent</h2>
<p>Besides inline events, you can also use the more powerful <code>handleEvent</code> interface. We prefer this over inline events for the following reasons:</p>
<ol>
<li>It results in cleaner component markup--no inline events.</li>
<li>Event delegation. Really important when handling events on items of long list.</li>
<li>No callbacks! Everything is a component method.</li>
<li>No memory leaks caused by callback closure.</li>
<li>One centralized place to handle all of the component's events</li>
<li>Easy to remove or modify events.</li>
<li>No binding issues with <code>this</code>. The scope will be the class itself.</li>
<li>If you accidentally bind the same event more than once, because it is the same <code>handleEvent</code> object, the browser will only fire it once.</li>
</ol>
<h2>What the Heck is handleEvent?</h2>
<p>The TL:DR is, it's a replacement for event callbacks. Instead of writing a callback for the event, you pass it an object with a <code>handleEvent</code> method. When the browser sees that your provided an object for an event, it checks to see if there is a <code>handleEvent</code> method. If there is, it uses that for the event. When using this interface with a class component, we give the component a method called <code>handleEvent</code> and then pass the component itself to the event. That way the event examines the component class, finds the <code>handleEvent</code> method and uses that. Because the <code>handleEvent</code> is a method of component class, it has access to all the properties and methods of the class.</p>
<p>Time to refactor our <code>Hello</code> component using <code>handleEvent</code>:</p>
<p data-height="300" data-theme-id="6688" data-slug-hash="NwPaPa" data-default-tab="js,result" data-user="rbiggs" data-embed-version="2" data-pen-title="Composi Tuts - events-6" class="codepen">See the Pen <a href="https://codepen.io/rbiggs/pen/NwPaPa/">Composi Tuts - events-6</a> by Robert Biggs (<a href="https://codepen.io/rbiggs">@rbiggs</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>Our class now has a <code>handleEvent</code> method. Because it will be invoked by the event, it gets the event object as its parameter. We can use that to check what it was the user clicked. Since we want to capture a click on the button, we check the event target node name. If it equals "BUTTON", we execute the <code>announce</code> method. In the example above we are using the && operator to set up a conditional check. You can read more about using it in the tutorial for <a target='__blank' href='./conditional-rendering.html'>Conditional Rendering</a>. At the moment the above example does not work because it's lacking the initial event delegation. We handle that next with a lifecycle hook.</p>
<h2>Use a Lifecycle Hook to Add Event</h2>
<p>Although we have a <code>handleEvent</code> method on our component, click the button does nothing. Remember, <code>handleEvent</code> is a replacement for the event callback. To work, <code>handleEvent</code> needs an <code>addEventListener</code> registered on the component. We can do that using a lifecycle hook. The one to use for this is <code>componentDidMount</code>. As soon as the component is injected in the DOM we want to attach an event to it to use our <code>handleEvent</code> method:</p>
<p data-height="300" data-theme-id="6688" data-slug-hash="QOwqKm" data-default-tab="js,result" data-user="rbiggs" data-embed-version="2" data-pen-title="Composi Tuts - events-7" class="codepen">See the Pen <a href="https://codepen.io/rbiggs/pen/QOwqKm/">Composi Tuts - events-7</a> by Robert Biggs (<a href="https://codepen.io/rbiggs">@rbiggs</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>Notice how we set up the event listener in the <code>componentDidMount</code> hook. We designated a <code>click</code> event, and then we passed in <code>this</code> instead of a callback. Here <code>this</code> is the class itself. This means the scope of the <code>handleEvent</code> method will be the class itself, giving us access to all its properties and methods from inside the <code>handleEvent</code> method. There will be no weird binding issues like inline events.</p>
<p>We registered the <code>addEventListener</code> on the component's <code>element</code>. For a component the <code>element</code> property is the base parent element of all the other child elements. In the case of our <code>Hello</code> class, that will be the div tag. Because the click event is registered on the div, we can use the event target in the <code>handleEvent</code> method to test for many possible interactions. This gives use the ability to handle user interactions through event delegation in with a simple pattern of testing the event target, followed by <code>&&</code> and the class method to execute.</p>
<h2>Event Delegation</h2>
<p>Event delegation is an important technique to reduce memory use. In out above example we handle it fairly simply, mostly because there was only one event target to test for. In a complex component your might have many event targets. In such a case you need to add a test for each target you are interested in. To make that easier you might want to use ids or classes to be more specific:</p>
<pre><code class="language-javascript">handleEvent(e) {
const id = e.target.id
e === 'add-item' && this.addItem()
e === 'remove-item' && this.removeItem()
}</code></pre>
<p>By registering the click event on the component base element, we can test for as many event targets as we need.</p>
<h2>Dealing with Nested Children</h2>
<p>All the parents out there will probably sigh in agreement with the next state. Managing children can be complicated. We're talking about capturig the correct event target when dealing with a complex component stucture. To illustrate this, let's image we have a component that creates a list with list items like the following:</p>
<pre><code class="language-javascript"><li>
<h4>{person.name}</h4>
<h5>{person.job}</h5>
</li></code></pre>
<p>We want to capture a user click on the list item so we can alert the text content of the list item. You would think that we could do this:</p>
<pre><code class="language-javascript">handleEvent(e) {
e.target.nodeName === 'LI' && this.announce(e)
}
announce(e) {
alert(e.target.textContent)
}</code></pre>
<p>If you implement a component list with this event handling, you will find that its behavior is not consistent. Most of the time when you click on the list item, it does nothing, but sometimes when you click somewhere inbetween the child element, you get the announcement. What is going on here is that we are testing for an event target of <code>LI</code>. Most of the time you would be clicking on click components, either <code>h4</code> or <code>h5</code>. Therefore these would not be caught by our target check. To handle this we could update our code like this:</p>
<pre><code class="language-javascript">handleEvent(e) {
// The user clicked on the list item itself:
e.target.nodeName === 'LI' && this.announce(e)
// The user clicked on an H4:
e.target.nodeName === 'H4' && this.announce(e)
// The user clicked on an H5:
e.target.nodeName === 'H5' && this.announce(e)
}</code></pre>
<p>With this change, you can now click anywhere on the list item and you will get the announcement. However, this is ugly, and not necessary. There is an easier way.</p>
<h2>Use Closest for Event Delegation</h2>
<p>Modern browsers have an Element method called <code>closest</code>. If you've used jQuery in the past, this works exactly the same as the jQuery function <code>closest</code>. You execute it on an element and pass it a selector to find the closest match. As we mentioned, this is available in modern browsers, including Microsoft Edge. However, if you need to support IE 9, 10 or 11, you can <a target="_blank" href="https://www.npmjs.com/package/element-closest">use the polyfill</a>. Here's the previous example redone with <code>Element.closest</code> to clean up our last example:</p>
<pre><code class="language-javascript">handleEvent(e) {
// The user clicked on the list item:
e.target.nodeName === 'LI' && this.announce(e)
// The user clicked on some child element, so use closest:
e.target.closest('li') && this.announce(e)
}</code></pre>
<p>Now this list item can have as many child elements as needed without making it complicated to capture the event target.</p>
<h2>Removing the Event for handleEvent</h2>
<p>If you plan on deleting a component from the DOM, you need to unbind its eventListener. This is really easy when you use <code>handleEvent</code>. Just provide the event and <code>this</code>. Composi provides a method to remove a component: <code>unmount</code>. To remove the event and unmount the component, we could do this:</code></p>
<pre><code class="language-javascript">// Remove the event listener from the component component.
// We used handleEvent, so we pass "this" as second argument:
clock.element.removeEventListener('click', this)
// Unmount the component:
clock.unmount()</code></pre>
<p> </p>
<p> </p>
</div>
<aside>
<menu>
<ul class="tutorials__menu">
<li class="tutorials__menu__item">
<a href='./index.html'>Hello World</a>
</li>
<li class="tutorials__menu__item">
<a href='./introducing-jsx.html'>Introducing JSX</a>
</li>
<li class="tutorials__menu__item">
<a href='./rendering-elements.html'>Rendering Elements</a>
</li>
<li class="tutorials__menu__item">
<a href='./components-n-props.html'>Components and Props</a>
</li>
<li class="tutorials__menu__item">
<a href='./state-n-lifecycle.html'>State and Lifecycle</a>
</li>
<li class="tutorials__menu__item">
<a href='./conditional-rendering.html'>Conditional Rendering</a>
</li>
<li class="tutorials__menu__item selected">
<a href='./handling-events.html'>Handling Events</a>
</li>
<li class="tutorials__menu__item">
<a href='./lists-n-keys.html'>Lists and Keys</a>
</li>
<li class="tutorials__menu__item">
<a href='./forms.html'>Forms</a>
</li>
<li class="tutorials__menu__item">
<a href='./lifting-state-up.html'>Lifting State Up</a>
</li>
<li class="tutorials__menu__item">
<a href='./thinking-in-composi.html'>Thinking in Composi</a>
</li>
<li class="tutorials__menu__item">
<a href='./jsx-in-depth.html'>JSX In Depth</a>
</li>
<li class="tutorials__menu__item">
<a href='./advanced-state-management.html'>Advanced State Management</a>
</li>
<li class="tutorials__menu__item">
<a href='./composi-datastore.html'>Composi DataStore</a>
</li>
<li class="tutorials__menu__item">
<a href='./integrating-other-libs.html'>Integrating with Other Libraries</a>
</li>
<li class="tutorials__menu__item">
<a href='./composi-without-jsx.html'>Composi Without JSX</a>
</li>
<li class="tutorials__menu__item">
<a href='./immutable-data.html'>Immutable Data</a>
</li>
</ul>
</menu>
</aside>
</section>
</article>
<footer>
<section>
<svg id='composi-logo-footer' viewBox="0 0 300 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Composi Logo</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Composi-Logo-Solid" fill="rgba(255,255,255,0.5)">
<path d="M1.77635684e-15,0 L95,0 L95,38 L209,38 L209,0 L300,0 L300,94 L265,93.8571663 L265,209 L300,209 L300,300 L209,300 L209,265 L95,265 L95,300 L1.77635684e-15,300 L1.77635684e-15,209 L40,209 L40,94 L1.77635684e-15,93.8571663 L1.77635684e-15,0 Z M107,107 L107,192 L192,192 L192,107 L107,107 Z" id="Combined-Shape"></path>
</g>
</g>
</svg>
<h3>Composi is open source (MIT) and available on <a href='https://github.com/composor/composi' target='__blank'>Github</a> and <a href="https://www.npmjs.com/package/composi" target='__blank'>NPM</a>.</h3>
</section>
</footer>
<script src="../js/prism.min.js"></script>
<script src="../js/prism-jsx.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-115293685-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-115293685-1');
</script>
</body>
</html>