34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introducing Redux with Lightning web components(LWC)
2 |
3 | [LWC-redux](http://www.lwc-redux.com/) is a [LWC](https://developer.salesforce.com/blogs/2018/12/introducing-lightning-web-components.html) binding for [Redux](https://redux.js.org/introduction/getting-started#basic-example). It will allow your Lightning Web Components to read data from a Redux store, and dispatch actions to the store to update data.
4 |
5 | LWC-redux helps you to write application that behaves consistently and provide state management to the application. It's also separate the JS business and design layers.
6 |
7 | ## Installation
8 |
9 | To install the LWC-redux, we only need to click on the below button. It will redirect you to another page where you can select production/sandbox org and proceed with 'Login to Salesforce' button.
10 | [Click here to install](http://www.lwc-redux.com/quick-start#installation)
11 |
12 | There are two types of developer processes or models supported in Salesforce Extensions for VS Code and Salesforce CLI. These models are explained below. Each model offers pros and cons and is fully supported.
13 |
14 | ## Why Use LWC Redux?
15 |
16 | Redux itself is a standalone library that can be used with any UI layer or framework, including LWC, React, Angular, Vue, Ember, and vanilla JS. Although Redux and React are commonly used together, they are independent of each other.
17 |
18 | If you are using Redux with any kind of UI framework, you will normally use a "UI binding" library to tie Redux together with your UI framework, rather than directly interacting with the store from your UI code.
19 |
20 | LWC Redux is the Redux UI binding library for LWC. If you are using Redux and LWC together, you should also use LWC Redux to bind these two libraries.
21 |
22 | ##### It is the Redux UI Bindings for LWC.
23 |
24 | ##### It Encourages Good LWC Architecture.
25 |
26 | ##### It Implements Performance Optimizations For You
27 |
28 | [Click here for more information](http://www.lwc-redux.com/why-use-lwc-redux)
29 |
30 | ## Basic Tutorial
31 |
32 | To see how to use LWC Redux in practice, we’ll show a step-by-step example by creating a todo list app.
33 |
34 | [Click here for basic tutorial](http://www.lwc-redux.com/basic-tutorial)
35 |
36 | ## Examples
37 |
38 | Go to Examples folder in the repository or [refer examples page](http://www.lwc-redux.com/examples)
39 |
40 | ## CCI Integration
41 |
42 | Add to your cumulusci.yml:
43 |
44 | ```
45 | sources:
46 | lwc-redux:
47 | github: https://github.com/chandrakiran-dev/lwc-redux
48 | ```
49 |
50 | Add to your cci flow:
51 |
52 | ```
53 | task: lwc-redux:deploy
54 | options:
55 | path: force-app/main/default
56 | ```
57 |
--------------------------------------------------------------------------------
/examples/lwc/stopWatchReducers/stopWatchReducers.js:
--------------------------------------------------------------------------------
1 | import {START_ACTION, STOP_ACTION, RESET_ACTION, TICK_ACTION, CREATE_LAP_ACTION} from 'c/stopWatchConstant';
2 |
3 | let initialLapData = {
4 | milliSec : 0,
5 | sec : 0,
6 | min : 0,
7 | hour : 0
8 | }
9 |
10 | let initialState = {
11 | milliSec : 0,
12 | sec : 0,
13 | min : 0,
14 | hour : 0,
15 | interval : undefined,
16 | laps : [],
17 | lapData : initialLapData
18 | }
19 |
20 |
21 |
22 | const reducers = (state = initialState, action) => {
23 | switch (action.type) {
24 | case START_ACTION:
25 | return {
26 | ...state,
27 | interval : action.payload.interval
28 | }
29 | case TICK_ACTION:{
30 | let tempState = {...state};
31 | tempState.milliSec = ++tempState.milliSec;
32 | tempState.lapData.milliSec = ++tempState.lapData.milliSec
33 | if (tempState.milliSec === 100) {
34 | tempState.milliSec = 0;
35 | tempState.sec = ++tempState.sec;
36 | }
37 | if (tempState.lapData.milliSec === 100) {
38 | tempState.lapData.milliSec = 0;
39 | tempState.lapData.sec = ++tempState.lapData.sec;
40 | }
41 |
42 | if (tempState.sec === 60) {
43 | tempState.min = ++tempState.min;
44 | tempState.sec = 0;
45 | }
46 | if (tempState.lapData.sec === 60) {
47 | tempState.lapData.min = ++tempState.lapData.min;
48 | tempState.lapData.sec = 0;
49 | }
50 |
51 | if (tempState.min === 60) {
52 | tempState.min = 0;
53 | tempState.hour = ++tempState.hour;
54 | }
55 | if (tempState.lapData.min === 60) {
56 | tempState.lapData.min = 0;
57 | tempState.lapData.hour = ++tempState.lapData.hour;
58 | }
59 | return JSON.parse(JSON.stringify(tempState));
60 | }
61 | case STOP_ACTION: {
62 | clearInterval(state.interval)
63 | return {
64 | ...state,
65 | interval: undefined
66 | }
67 | }
68 | case RESET_ACTION : {
69 | return initialState;
70 | }
71 | case CREATE_LAP_ACTION : {
72 | let tempLap = [...state.laps]
73 | tempLap.unshift({hour: state.lapData.hour, min: state.lapData.min, sec: state.lapData.sec, milliSec: state.lapData.milliSec});
74 | return {
75 | ...state,
76 | laps: tempLap,
77 | lapData: initialLapData
78 | }
79 | }
80 | default: return state;
81 | }
82 | }
83 | export default reducers;
--------------------------------------------------------------------------------
/force-app/main/default/lwc/lwcRedux/lwcRedux.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is lwc-redux component js file. This expose all the method that can be use in other components.
3 | *
4 | * @author : https://github.com/chandrakiran-dev
5 | */
6 |
7 | import ReduxElement from './reduxElement';
8 | import {registerListener, unregisterAllListeners} from './reduxHandler';
9 | export const combineReducers = reducers => {
10 | try{
11 | return window.Redux.combineReducers(reducers);
12 | }
13 | catch(error){
14 | console.error(error)
15 | }
16 | }
17 |
18 | export const createStore = (reducers, logger) => {
19 | try{
20 | let middleware;
21 | if(logger){
22 | middleware = window.Redux.applyMiddleware(window.ReduxThunk.default, logger);
23 | }else{
24 | middleware = window.Redux.applyMiddleware(window.ReduxThunk.default);
25 | }
26 | return window.Redux.createStore(reducers, middleware);
27 | }
28 | catch(error){
29 | console.error(error)
30 | }
31 | }
32 |
33 | export const createLogger = (logger = initialLogger) => {
34 | try{
35 | logger = {...initialLogger, ...logger};
36 | return window.reduxLogger.createLogger(logger);
37 | }
38 | catch(error){
39 | console.error(error)
40 | }
41 | }
42 |
43 | export const bindActionCreators = (actions, dispatch) => {
44 | try{
45 | return window.Redux.bindActionCreators(actions, dispatch);
46 | }
47 | catch(error){
48 | console.error(error)
49 | }
50 | }
51 |
52 | const getStore = (thisArg, callback) =>{
53 | try{
54 | const eventStore = new CustomEvent('lwcredux__getstore', { bubbles: true,composed: true, detail : (store)=>{
55 | callback(store);
56 | }})
57 | if(eventStore){
58 | thisArg.dispatchEvent(eventStore);
59 | }
60 | }
61 | catch(error){
62 | console.error(error)
63 | }
64 | }
65 |
66 | export const Redux = (Superclass = Object) => {
67 | return ReduxElement
68 | }
69 |
70 | const initialLogger = {
71 | level: 'log',
72 | logger: console,
73 | logErrors: true,
74 | collapsed: undefined,
75 | predicate: undefined,
76 | duration: false, // By default, duration is false
77 | timestamp: true,
78 | stateTransformer: state => JSON.parse(JSON.stringify(state)),
79 | actionTransformer: action => JSON.parse(JSON.stringify(action)),
80 | errorTransformer: error => JSON.parse(JSON.stringify(error)),
81 | colors: {
82 | title: () => 'inherit',
83 | prevState: () => '#9E9E9E',
84 | action: () => '#03A9F4',
85 | nextState: () => '#4CAF50',
86 | error: () => '#F20404',
87 | },
88 | diff: false, // By default, diff is false
89 | diffPredicate: undefined,
90 | };
91 |
92 |
93 | export {ReduxElement, registerListener, unregisterAllListeners};
94 |
95 |
--------------------------------------------------------------------------------
/force-app/main/default/lwc/lwcRedux/reduxElement.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file contains the ReduxElement that extends the LightningElement. This will be the parent component for all redux component
3 | *
4 | * @author : https://github.com/chandrakiran-dev
5 | */
6 |
7 | import { LightningElement, track } from "lwc";
8 | import { bindActionCreators } from "./lwcRedux";
9 | const getStore = (thisArg, callback) => {
10 | try {
11 | const eventStore = new CustomEvent("lwcredux__getstore", {
12 | bubbles: true,
13 | composed: true,
14 | detail: store => {
15 | callback(store);
16 | }
17 | });
18 | if (eventStore) {
19 | thisArg.dispatchEvent(eventStore);
20 | }
21 | } catch (error) {
22 | console.error(error);
23 | }
24 | };
25 |
26 | const prepareProps = (thisArg, store) => {
27 | try {
28 | if (thisArg.mapStateToProps) {
29 | const state = thisArg.mapStateToProps(store.getState());
30 | return Object.assign({}, thisArg.props, state);
31 | }
32 | } catch (error) {
33 | console.error(error);
34 | }
35 | return thisArg.props;
36 | };
37 |
38 | export default class ReduxElement extends LightningElement {
39 | @track props = {};
40 | oldState;
41 | _unsubscribe;
42 | connectedCallback() {
43 | getStore(this, store => {
44 | if (store) {
45 | try {
46 | let actions = {};
47 | if (this.mapDispatchToProps) {
48 | actions = this.mapDispatchToProps();
49 | }
50 | this.props = prepareProps(this, store);
51 | this.props = Object.assign({}, this.props, bindActionCreators(actions, store.dispatch));
52 | this._unsubscribe = store.subscribe(this._handleStoreChange.bind(this));
53 | } catch (error) {
54 | console.error(error);
55 | }
56 | }
57 | });
58 | }
59 |
60 | disconnectedCallback() {
61 | if (this._unsubscribe) {
62 | this._unsubscribe();
63 | }
64 | }
65 |
66 | _forceUpdate() {
67 | getStore(this, store => {
68 | try {
69 | if (store) {
70 | this.props = prepareProps(this, store);
71 | }
72 | } catch (error) {
73 | console.error(error);
74 | }
75 | });
76 | this.props = Object.assign({}, this.props);
77 | }
78 |
79 | _handleStoreChange() {
80 | this._updateOnlyOnChange();
81 | }
82 |
83 | _updateOnlyOnChange(){
84 | getStore(this, store => {
85 | try {
86 | if (store) {
87 | if (this.mapStateToProps) {
88 | const state = this.mapStateToProps(store.getState());
89 | let reload = false;
90 | for(const key of Object.keys(state)){
91 | const value = state[key];
92 | const oldValue = this.oldState ? this.oldState[key] : undefined;
93 | if(value !== oldValue){
94 | reload = true;
95 | break;
96 | }
97 | }
98 | this.oldState = state
99 | if(reload){
100 | this.props = Object.assign({}, this.props, state);
101 | }
102 |
103 | }
104 | }
105 | } catch (error) {
106 | console.error(error);
107 | }
108 | });
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/force-app/main/default/staticresources/redux.js:
--------------------------------------------------------------------------------
1 | !function (e, t) { "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t(e.Redux = {}) }(this, function (e) { "use strict"; var t = function (e) { var t, r = e.Symbol; return "function" == typeof r ? r.observable ? t = r.observable : (t = r("observable"), r.observable = t) : t = "@@observable", t }("undefined" != typeof self ? self : "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof module ? module : Function("return this")()), r = function () { return Math.random().toString(36).substring(7).split("").join(".") }, n = { INIT: "@@redux/INIT" + r(), REPLACE: "@@redux/REPLACE" + r(), PROBE_UNKNOWN_ACTION: function () { return "@@redux/PROBE_UNKNOWN_ACTION" + r() } }; function o(e, t) { var r = t && t.type; return "Given " + (r && 'action "' + r + '"' || "an action") + ', reducer "' + e + '" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.' } function i(e, t) { return function () { return t(e.apply(this, arguments)) } } function u(e, t, r) { return t in e ? Object.defineProperty(e, t, { value: r, enumerable: !0, configurable: !0, writable: !0 }) : e[t] = r, e } function a() { for (var e = arguments.length, t = Array(e), r = 0; e > r; r++)t[r] = arguments[r]; return 0 === t.length ? function (e) { return e } : 1 === t.length ? t[0] : t.reduce(function (e, t) { return function () { return e(t.apply(void 0, arguments)) } }) } e.createStore = function e(r, o, i) { var u; if ("function" == typeof o && "function" == typeof i || "function" == typeof i && "function" == typeof arguments[3]) throw Error("It looks like you are passing several store enhancers to createStore(). This is not supported. Instead, compose them together to a single function"); if ("function" == typeof o && void 0 === i && (i = o, o = void 0), void 0 !== i) { if ("function" != typeof i) throw Error("Expected the enhancer to be a function."); return i(e)(r, o) } if ("function" != typeof r) throw Error("Expected the reducer to be a function."); var a = r, c = o, f = [], s = f, d = !1; function l() { s === f && (s = f.slice()) } function p() { if (d) throw Error("You may not call store.getState() while the reducer is executing. The reducer has already received the state as an argument. Pass it down from the top reducer instead of reading it from the store."); return c } function h(e) { if ("function" != typeof e) throw Error("Expected the listener to be a function."); if (d) throw Error("You may not call store.subscribe() while the reducer is executing. If you would like to be notified after the store has been updated, subscribe from a component and invoke store.getState() in the callback to access the latest state. See https://redux.js.org/api-reference/store#subscribe(listener) for more details."); var t = !0; return l(), s.push(e), function () { if (t) { if (d) throw Error("You may not unsubscribe from a store listener while the reducer is executing. See https://redux.js.org/api-reference/store#subscribe(listener) for more details."); t = !1, l(); var r = s.indexOf(e); s.splice(r, 1) } } } function y(e) { if (!function (e) { if ("object" != typeof e || null === e) return !1; for (var t = e; null !== Object.getPrototypeOf(t);)t = Object.getPrototypeOf(t); return Object.getPrototypeOf(e) === t }(e)) throw Error("Actions must be plain objects. Use custom middleware for async actions."); if (void 0 === e.type) throw Error('Actions may not have an undefined "type" property. Have you misspelled a constant?'); if (d) throw Error("Reducers may not dispatch actions."); try { d = !0, c = a(c, e) } finally { d = !1 } for (var t = f = s, r = 0; t.length > r; r++)(0, t[r])(); return e } return y({ type: n.INIT }), (u = { dispatch: y, subscribe: h, getState: p, replaceReducer: function (e) { if ("function" != typeof e) throw Error("Expected the nextReducer to be a function."); a = e, y({ type: n.REPLACE }) } })[t] = function () { var e, r = h; return (e = { subscribe: function (e) { if ("object" != typeof e || null === e) throw new TypeError("Expected the observer to be an object."); function t() { e.next && e.next(p()) } return t(), { unsubscribe: r(t) } } })[t] = function () { return this }, e }, u }, e.combineReducers = function (e) { for (var t = Object.keys(e), r = {}, i = 0; t.length > i; i++) { var u = t[i]; "function" == typeof e[u] && (r[u] = e[u]) } var a, c = Object.keys(r); try { !function (e) { Object.keys(e).forEach(function (t) { var r = e[t]; if (void 0 === r(void 0, { type: n.INIT })) throw Error('Reducer "' + t + "\" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined."); if (void 0 === r(void 0, { type: n.PROBE_UNKNOWN_ACTION() })) throw Error('Reducer "' + t + "\" returned undefined when probed with a random type. Don't try to handle " + n.INIT + ' or other actions in "redux/*" namespace. They are considered private. Instead, you must return the current state for any unknown actions, unless it is undefined, in which case you must return the initial state, regardless of the action type. The initial state may not be undefined, but can be null.') }) }(r) } catch (e) { a = e } return function (e, t) { if (void 0 === e && (e = {}), a) throw a; for (var n = !1, i = {}, u = 0; c.length > u; u++) { var f = c[u], s = e[f], d = (0, r[f])(s, t); if (void 0 === d) { var l = o(f, t); throw Error(l) } i[f] = d, n = n || d !== s } return n ? i : e } }, e.bindActionCreators = function (e, t) { if ("function" == typeof e) return i(e, t); if ("object" != typeof e || null === e) throw Error("bindActionCreators expected an object or a function, instead received " + (null === e ? "null" : typeof e) + '. Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?'); for (var r = Object.keys(e), n = {}, o = 0; r.length > o; o++) { var u = r[o], a = e[u]; "function" == typeof a && (n[u] = i(a, t)) } return n }, e.applyMiddleware = function () { for (var e = arguments.length, t = Array(e), r = 0; e > r; r++)t[r] = arguments[r]; return function (e) { return function () { var r = e.apply(void 0, arguments), n = function () { throw Error("Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.") }, o = { getState: r.getState, dispatch: function () { return n.apply(void 0, arguments) } }, i = t.map(function (e) { return e(o) }); return function (e) { for (var t = 1; arguments.length > t; t++) { var r = null != arguments[t] ? arguments[t] : {}, n = Object.keys(r); "function" == typeof Object.getOwnPropertySymbols && (n = n.concat(Object.getOwnPropertySymbols(r).filter(function (e) { return Object.getOwnPropertyDescriptor(r, e).enumerable }))), n.forEach(function (t) { u(e, t, r[t]) }) } return e }({}, r, { dispatch: n = a.apply(void 0, i)(r.dispatch) }) } } }, e.compose = a, e.__DO_NOT_USE__ActionTypes = n, Object.defineProperty(e, "__esModule", { value: !0 }) });
--------------------------------------------------------------------------------
/force-app/main/default/staticresources/reduxLogger.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.reduxLogger=e.reduxLogger||{})}(this,function(e){"use strict";function t(e,t){return r=t-e.toString().length,new Array(1+r).join("0")+e;var r}var r,n,L=function(e){return t(e.getHours(),2)+":"+t(e.getMinutes(),2)+":"+t(e.getSeconds(),2)+"."+t(e.getMilliseconds(),3)},p="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance:Date,v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},M=function(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t=o.length?i(new w(c,p,new m(void 0,n[p]))):j(n[p],o[p],i,a,c,p,l);for(;p