set<\/code> trap, we can keep an eye on changes that are made to the object. This is the main part we’re interested in today. Add the following straight after the last lines that you added and we’ll discuss what it’s doing:<\/p>\nself.state = new Proxy((params.state || {}), {\r\n set: function(state, key, value) {\r\n\r\n state[key] = value;\r\n\r\n console.log(`stateChange: ${key}: ${value}`);\r\n\r\n self.events.publish('stateChange', self.state);\r\n\r\n if(self.status !== 'mutation') {\r\n console.warn(`You should use a mutation to set ${key}`);\r\n }\r\n\r\n self.status = 'resting';\r\n\r\n return true;\r\n }\r\n});<\/code><\/pre>\nWhat’s happening here is we’re trapping the state object set<\/code> operations. That means that when a mutation runs something like state.name = 'Foo'<\/code> , this trap catches it before it can be set and provides us an opportunity to work with the change or even reject it completely. In our context though, we’re setting the change and then logging it to the console. We’re then publishing a stateChange<\/code> event with our PubSub<\/code> module. Anything subscribed to that event’s callback will be called. Lastly, we’re checking the status of Store<\/code>. If it’s not currently running a mutation<\/code>, it probably means that the state was updated manually. We add a little warning in the console for that to give the developer a little telling off.<\/p>\nThere’s a lot going on there, but I hope you’re starting to see how this is all coming together and importantly, how we’re able to maintain state centrally, thanks to Proxy and Pub\/Sub.<\/p>\n
Dispatch and commit<\/h4>\n
Now that we’ve added our core elements of the Store<\/code>, let’s add two methods. One that will call our actions<\/code> named dispatch<\/code> and another that will call our mutations<\/code> called commit<\/code>. Let’s start with dispatch<\/code> by adding this method after your constructor<\/code> in store.js<\/code>:<\/p>\ndispatch(actionKey, payload) {\r\n\r\n let self = this;\r\n\r\n if(typeof self.actions[actionKey] !== 'function') {\r\n console.error(`Action \"${actionKey} doesn't exist.`);\r\n return false;\r\n }\r\n\r\n console.groupCollapsed(`ACTION: ${actionKey}`);\r\n\r\n self.status = 'action';\r\n\r\n self.actions[actionKey](self, payload);\r\n\r\n console.groupEnd();\r\n\r\n return true;\r\n}<\/code><\/pre>\nThe process here is: look for an action and, if it exists, set a status and call the action while creating a logging group that keeps all of our logs nice and neat. Anything that is logged (like a mutation or Proxy log) will be kept in the group that we define. If no action is set, it’ll log an error and bail. That was pretty straightforward, and the commit<\/code> method is even more straightforward. <\/p>\nAdd this after your dispatch<\/code> method:<\/p>\ncommit(mutationKey, payload) {\r\n let self = this;\r\n\r\n if(typeof self.mutations[mutationKey] !== 'function') {\r\n console.log(`Mutation \"${mutationKey}\" doesn't exist`);\r\n return false;\r\n }\r\n\r\n self.status = 'mutation';\r\n\r\n let newState = self.mutations[mutationKey](self.state, payload);\r\n\r\n self.state = Object.assign(self.state, newState);\r\n\r\n return true;\r\n}<\/code><\/pre>\nThis method is pretty similar, but let’s run through the process anyway. If the mutation can be found, we run it and get our new state from its return value. We then take that new state and merge it with our existing state to create an up-to-date version of our state.<\/p>\n
With those methods added, our Store<\/code> object is pretty much complete. You could actually modular-ize this application now if you wanted because we’ve added most of the bits that we need. You could also add some tests to check that everything run as expected. But I’m not going to leave you hanging like that. Let’s make it all actually do what we set out to do and continue with our little app!<\/p>\nCreating a base component<\/h3>\n
To communicate with our store, we’ve got three main areas that update independently based on what’s stored in it. We’re going to make a list of submitted items, a visual count of those items, and another one that’s visually hidden with more accurate information for screen readers. These all do different things, but they would all benefit from something shared to control their local state. We’re going to make a base component class!<\/p>\n
First up, let’s create a file. In the lib<\/code> directory, go ahead and create a file called component.js<\/code>. The path for me is: <\/p>\n~\/Documents\/Projects\/vanilla-js-state-management-boilerplate\/src\/js\/lib\/component.js<\/code><\/pre>\nOnce that file is created, open it and add the following:<\/p>\n
import Store from '..\/store\/store.js';\r\n\r\nexport default class Component {\r\n constructor(props = {}) {\r\n let self = this;\r\n\r\n this.render = this.render || function() {};\r\n\r\n if(props.store instanceof Store) {\r\n props.store.events.subscribe('stateChange', () => self.render());\r\n }\r\n\r\n if(props.hasOwnProperty('element')) {\r\n this.element = props.element;\r\n }\r\n }\r\n}<\/code><\/pre>\nLet’s talk through this chunk of code. First up, we’re importing the Store<\/code> class<\/em>. This isn’t because we want an instance of it, but more for checking one of our properties in the constructor<\/code>. Speaking of which, in the constructor<\/code> we’re looking to see if we’ve got a render method. If this Component<\/code> class is the parent of another class, then that will have likely set its own method for render<\/code>. If there is no method set, we create an empty method that will prevent things from breaking.<\/p>\nAfter this, we do the check against the Store<\/code> class like I mentioned above. We do this to make sure that the store<\/code> prop is a Store<\/code> class instance so we can confidently use its methods and properties. Speaking of which, we’re subscribing to the global stateChange<\/code> event so our object can react<\/em>. This is calling the render<\/code> function each time the state changes.<\/p>\nThat’s all we need to write for that class. It’ll be used as a parent class that other components classes will extend<\/code>. Let’s crack on with those!<\/p>\nCreating our components<\/h3>\n
Like I said earlier, we’ve got three components to make and their all going to extend<\/code> the base Component<\/code> class. Let’s start off with the biggest one: the list of items!<\/p>\nIn your js<\/code> directory, create a new folder called components<\/code> and in there create a new file called list.js<\/code>. For me the path is: <\/p>\n~\/Documents\/Projects\/vanilla-js-state-management-boilerplate\/src\/js\/components\/list.js<\/code><\/pre>\nOpen up that file and paste this whole chunk of code in there:<\/p>\n
import Component from '..\/lib\/component.js';\r\nimport store from '..\/store\/index.js';\r\n\r\nexport default class List extends Component {\r\n\r\n constructor() {\r\n super({\r\n store,\r\n element: document.querySelector('.js-items')\r\n });\r\n }\r\n\r\n render() {\r\n let self = this;\r\n\r\n if(store.state.items.length === 0) {\r\n self.element.innerHTML = `<p class=\"no-items\">You've done nothing yet 😢<\/p>`;\r\n return;\r\n }\r\n\r\n self.element.innerHTML = `\r\n <ul class=\"app__items\">\r\n ${store.state.items.map(item => {\r\n return `\r\n <li>${item}<button aria-label=\"Delete this item\">\u00d7<\/button><\/li>\r\n `\r\n }).join('')}\r\n <\/ul>\r\n `;\r\n\r\n self.element.querySelectorAll('button').forEach((button, index) => {\r\n button.addEventListener('click', () => {\r\n store.dispatch('clearItem', { index });\r\n });\r\n });\r\n }\r\n};<\/code><\/pre>\nI hope that code is pretty self-explanatory after what we’ve learned earlier in this tutorial, but let’s skim through it anyway. We start off by passing our Store<\/code> instance up to the Component<\/code> parent class that we are extending. This is the Component<\/code> class that we’ve just written.<\/p>\nAfter that, we declare our render method that gets called each time the stateChange<\/code> Pub\/Sub event happens. In this render<\/code> method we put out either a list of items, or a little notice if there are no items. You’ll also notice that each button has an event attached to it and they dispatch and action within our store. This action doesn’t exist yet, but we’ll get to it soon.<\/p>\nNext up, create two more files. These are two new components, but they’re tiny — so we’re just going to paste some code in them and move on.<\/p>\n
First, create count.js<\/code> in your component<\/code> directory and paste the following in it:<\/p>\nimport Component from '..\/lib\/component.js';\r\nimport store from '..\/store\/index.js';\r\n\r\nexport default class Count extends Component {\r\n constructor() {\r\n super({\r\n store,\r\n element: document.querySelector('.js-count')\r\n });\r\n }\r\n\r\n render() {\r\n let suffix = store.state.items.length !== 1 ? 's' : '';\r\n let emoji = store.state.items.length > 0 ? '🙌' : '😢';\r\n\r\n this.element.innerHTML = `\r\n <small>You've done<\/small>\r\n ${store.state.items.length}<\/span>\r\n <small>thing${suffix} today ${emoji}<\/small>\r\n `;\r\n }\r\n}<\/code><\/pre>\nLooks pretty similar to list, huh? There’s nothing in here that we haven’t already covered, so let’s add another file. In the same components<\/code> directory add a status.js<\/code> file and paste the following in it:<\/p>\nimport Component from '..\/lib\/component.js';\r\nimport store from '..\/store\/index.js';\r\n\r\nexport default class Status extends Component {\r\n constructor() {\r\n super({\r\n store,\r\n element: document.querySelector('.js-status')\r\n });\r\n }\r\n\r\n render() {\r\n let self = this;\r\n let suffix = store.state.items.length !== 1 ? 's' : '';\r\n\r\n self.element.innerHTML = `${store.state.items.length} item${suffix}`;\r\n }\r\n}<\/code><\/pre>\nAgain, we’ve covered everything in there, but you can see how handy it is having a base Component<\/code> to work with, right? That’s one of the many benefits of Object-orientated Programming<\/a>, which is what most of this tutorial is based on.<\/p>\nFinally, let’s check that your js<\/code> directory is looking right. This is the structure of where we’re currently at:<\/p>\n\/src\r\n\u251c\u2500\u2500 js\r\n\u2502 \u251c\u2500\u2500 components\r\n\u2502 \u2502 \u251c\u2500\u2500 count.js\r\n\u2502 \u2502 \u251c\u2500\u2500 list.js\r\n\u2502 \u2502 \u2514\u2500\u2500 status.js\r\n\u2502 \u251c\u2500\u2500lib\r\n\u2502 \u2502 \u251c\u2500\u2500component.js\r\n\u2502 \u2502 \u2514\u2500\u2500pubsub.js\r\n\u2514\u2500\u2500\u2500\u2500\u2500 store\r\n \u2514\u2500\u2500store.js\r\n \u2514\u2500\u2500main.js<\/code><\/pre>\nLet’s wire it up<\/h3>\n
Now that we’ve got our front-end components and our main Store<\/code>, all we’ve got to do is wire it all up.<\/p>\nWe’ve got our store system and the components to render and interact with its data. Let’s now wrap up by hooking up the two separate ends of the app and make the whole thing work together. We\u2019ll need to add an initial state, some actions<\/code> and some mutations<\/code>. In your store<\/code> directory, add a new file called state.js<\/code>. For me it’s like this: <\/p>\n~\/Documents\/Projects\/vanilla-js-state-management-boilerplate\/src\/js\/store\/state.js<\/code><\/pre>\nOpen up that file and add the following:<\/p>\n
export default {\r\n items: [\r\n 'I made this',\r\n 'Another thing'\r\n ]\r\n};<\/code><\/pre>\nThis is pretty self-explanatory. We’re adding a default set of items so that on first-load, our little app will be fully interactive. Let’s move on to some actions<\/code>. In your store<\/code> directory, create a new file called actions.js<\/code> and add the following to it:<\/p>\nexport default {\r\n addItem(context, payload) {\r\n context.commit('addItem', payload);\r\n },\r\n clearItem(context, payload) {\r\n context.commit('clearItem', payload);\r\n }\r\n};<\/code><\/pre>\nThe actions in this app are pretty minimal. Essentially, each action is passing a payload to a mutation, which in turn, commits the data to store. The context<\/code>, as we learned earlier, is the instance of the Store<\/code> class and the payload<\/code> is passed in by whatever dispatches the action. Speaking of mutations, let’s add some. In this same directory add a new file called mutations.js<\/code>. Open it up and add the following:<\/p>\nexport default {\r\n addItem(state, payload) {\r\n state.items.push(payload);\r\n\r\n return state;\r\n },\r\n clearItem(state, payload) {\r\n state.items.splice(payload.index, 1);\r\n\r\n return state;\r\n }\r\n};<\/code><\/pre>\nLike the actions, these mutations are minimal. In my opinion, your mutations should always be simple because they have one job: mutate the store’s state. As a result, these examples are as complex as they should ever be. Any proper logic should happen in your actions<\/code>. As you can see for this system, we return the new version of the state so that the Store`'s <code>commit<\/code> method can do its magic and update everything. With that, the main elements of the store system are in place. Let’s glue them together with an index file.<\/p>\nIn the same directory, create a new file called index.js<\/code>. Open it up and add the following:<\/p>\nimport actions from '.\/actions.js';\r\nimport mutations from '.\/mutations.js';\r\nimport state from '.\/state.js';\r\nimport Store from '.\/store.js';\r\n\r\nexport default new Store({\r\n actions,\r\n mutations,\r\n state\r\n});<\/code><\/pre>\nAll this file is doing is importing all of our store pieces and glueing them all together as one succinct Store<\/code> instance. Job done!<\/p>\nThe final piece of the puzzle<\/h3>\n
The last thing we need to put together is the main.js<\/code> file that we included in our index.html<\/code> page waaaay<\/em> up at the start of this tutorial. Once we get this sorted, we’ll be able to fire up our browsers and enjoy our hard work! Create a new file called main.js<\/code> at the root of your js<\/code> directory. This is how it looks for me:<\/p>\n~\/Documents\/Projects\/vanilla-js-state-management-boilerplate\/src\/js\/main.js<\/code><\/pre>\nOpen it up and add the following:<\/p>\n
import store from '.\/store\/index.js'; \r\n\r\nimport Count from '.\/components\/count.js';\r\nimport List from '.\/components\/list.js';\r\nimport Status from '.\/components\/status.js';\r\n\r\nconst formElement = document.querySelector('.js-form');\r\nconst inputElement = document.querySelector('#new-item-field');<\/code><\/pre>\nSo far, all we’re doing is pulling in dependencies that we need. We’ve got our Store<\/code>, our front-end components and a couple of DOM elements to work with. Let’s add this next bit to make the form interactive, straight under that code:<\/p>\nformElement.addEventListener('submit', evt => {\r\n evt.preventDefault();\r\n\r\n let value = inputElement.value.trim();\r\n\r\n if(value.length) {\r\n store.dispatch('addItem', value);\r\n inputElement.value = '';\r\n inputElement.focus();\r\n }\r\n});<\/code><\/pre>\nWhat we’re doing here is adding an event listener to the form and preventing it from submitting. We then grab the value of the textbox and trim any whitespace off it. We do this because we want to check if there’s actually any content to pass to the store next. Finally, if there’s content, we dispatch our addItem<\/code> action with that content and let our shiny new store<\/code> deal with it for us.<\/p>\nLet’s add some more code to main.js<\/code>. Under the event listener, add the following:<\/p>\nconst countInstance = new Count();\r\nconst listInstance = new List();\r\nconst statusInstance = new Status();\r\n\r\ncountInstance.render();\r\nlistInstance.render();\r\nstatusInstance.render();<\/code><\/pre>\nAll we’re doing here is creating new instances of our components and calling each of their render<\/code> methods so that we get our initial state on the page. <\/p>\nWith that final addition, we are done!<\/p>\n
Open up your browser, refresh and bask in the glory of your new state managed app. Go ahead and add something like “Finished this awesome tutorial”<\/em> in there. Pretty neat, huh?<\/p>\nNext steps<\/h3>\n
There’s a lot of stuff you could do with this little system that we’ve put together. Here are some ideas for taking it further on your own:<\/p>\n
\n- You could implement some local storage to maintain state, even when you reload<\/li>\n
- You could pull out the front-end of this and have a little state system for your projects<\/li>\n
- You could continue to develop the front-end of this app and make it look awesome. (I’d be really interested to see your work, so please share!)<\/li>\n
- You could work with some remote data and maybe even an API<\/li>\n
- You could take what you’ve learned about
Proxy<\/code> and the Pub\/Sub pattern and develop those transferable skills further<\/li>\n<\/ul>\nWrapping up<\/h3>\n
Thanks for learning about how these state systems work with me. The big, popular ones are much more complex and smarter that what we’ve done — but it’s still useful to get an idea of how these systems work and unravel the mystery behind them. It’s also useful to learn how powerful JavaScript can be with no frameworks whatsoever.<\/p>\n
If you want a finished version of this little system, check out this GitHub repository<\/a>. You can also see a demo here<\/a>.<\/p>\nIf you develop on this further, I’d love to see it, so hit me up on