├── .gitignore ├── .babelrc ├── package.json ├── webpack.config.js ├── src ├── store.js ├── App.js ├── template.html └── components │ └── Counter.js └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counters", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "watch": "webpack-dev-server --mode=development", 8 | "build": "webpack --mode=production" 9 | }, 10 | "devDependencies": { 11 | "html-webpack-plugin": "^3.2.0", 12 | "webpack": "^4.28.4", 13 | "webpack-cli": "^3.2.1", 14 | "webpack-dev-server": "^3.1.14", 15 | "@babel/core": "^7.2.2", 16 | "@babel/preset-env": "^7.2.3", 17 | "babel-loader": "^8.0.5" 18 | }, 19 | "dependencies": { 20 | "@webcomponents/webcomponentsjs": "^2.2.4", 21 | "redux": "^4.0.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const htmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | 6 | entry: './src/App.js', 7 | 8 | output: { 9 | filename: 'bundle.js', 10 | path: path.resolve(__dirname, 'build') 11 | }, 12 | 13 | module: { 14 | rules:[ 15 | { 16 | test: /\.js$/, 17 | exclude: /node_modules/, 18 | loader: 'babel-loader' 19 | } 20 | ] 21 | }, 22 | 23 | plugins: [ 24 | new htmlWebpackPlugin({ 25 | template: './src/template.html' 26 | }) 27 | ], 28 | 29 | devServer: { 30 | port: 9000, 31 | open: true 32 | } 33 | 34 | }; -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | 3 | export const INCREASE_COUNTER = 'INCREASE_COUNTE'; 4 | 5 | const initState = [ 6 | {uid:1, counter: 0}, 7 | {uid:2, counter: 0}, 8 | {uid:3, counter: 0} 9 | ]; 10 | 11 | const reducer = (state = initState, action) => { 12 | switch(action.type) { 13 | case INCREASE_COUNTER: 14 | return state.map((counterItem)=>{ 15 | if(counterItem.uid === action.uid) { 16 | return { ...counterItem, counter: counterItem.counter + 1 }; 17 | } 18 | return counterItem; 19 | }); 20 | default: 21 | return state; 22 | } 23 | } 24 | 25 | export default createStore(reducer); -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # 🏗️ Native Web Component With Redux 2 | 3 | This is an experimental project, where I tried out native web components. I created a Counter component and embeded it three times to a simple HTML page. After that, I connected them to a common store with Redux and voila! 4 | The counters can increase themselfes in store individually with their own object. 5 | 6 | ## 🧱 Would you give it a try?! 7 | 8 | * First step: clone the repo 9 | * Second step: install the dependencies with **yarn install** command 10 | * Thirs step: **yarn watch** command (your development server will run on the **port 9000**) 11 | 12 | ## 🍻 If you need a bundled, build version: **yarn build** 13 | 14 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import '@webcomponents/webcomponentsjs'; 2 | import Counter from './components/Counter'; 3 | import store from './store'; 4 | 5 | class App { 6 | 7 | constructor() { 8 | this.initComponents(); 9 | store.subscribe(()=>{ 10 | const altogether = store.getState().reduce((acc, currentCounter)=>{ return currentCounter.counter + acc}, 0) 11 | this.changeAltogether(altogether); 12 | }); 13 | } 14 | 15 | changeAltogether(altogether) { 16 | document.getElementById('altogether').innerHTML = altogether; 17 | } 18 | 19 | initComponents() { 20 | window.customElements.define('my-counter', Counter); 21 | } 22 | 23 | } 24 | 25 | (function(){ 26 | new App(); 27 | })(); 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Counters 8 | 9 | 28 | 29 | 30 |
31 | Altogether: 0 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import store, { INCREASE_COUNTER } from '../store'; 2 | 3 | class Counter extends HTMLElement { 4 | 5 | constructor() { 6 | super(); 7 | this.attachShadow({mode: 'open'}); 8 | this._render(); 9 | this._uid = this.getAttribute('uid'); 10 | this._number = store.getState().find((counter => counter.uid==this._uid)).counter || 0; 11 | this._theNumberDOM; 12 | this._addButtonDOM; 13 | this._subscribeStore(); 14 | } 15 | 16 | connectedCallback() { 17 | this._theNumberDOM = this.shadowRoot.getElementById('the-number'); 18 | this._addButtonDOM = this.shadowRoot.querySelector('.increase-button'); 19 | this._setNumberInDOM(this._number); 20 | this._handeOnClick(); 21 | } 22 | 23 | _subscribeStore() { 24 | store.subscribe(() => { 25 | this._setNumberInDOM(store.getState().find((counter => counter.uid==this._uid)).counter); 26 | }); 27 | } 28 | 29 | _setNumberInDOM(num = 0) { 30 | if(this._theNumberDOM) this._theNumberDOM.innerHTML = num; 31 | } 32 | 33 | _handeOnClick() { 34 | this._addButtonDOM.addEventListener('click', () => { 35 | store.dispatch({ 36 | type: INCREASE_COUNTER, 37 | uid: Number(this._uid) 38 | }); 39 | }); 40 | } 41 | 42 | _render() { 43 | this.shadowRoot.innerHTML = ` 44 | ${this._handleCSS()} 45 |
46 |

Counter: 0

47 | 48 |
49 | `; 50 | } 51 | 52 | _handleCSS() { 53 | return(` 54 | 83 | `); 84 | } 85 | 86 | attributeChangedCallback(name, oldValue, newValue) { 87 | if(name === 'uid') this._uid = newValue; 88 | } 89 | 90 | static get observedAttributes() { 91 | return ['uid']; 92 | } 93 | 94 | } 95 | 96 | export default Counter; --------------------------------------------------------------------------------