From 6d4887e4f02735a19b7aa60f9bfb5375e2d2adc8 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Sat, 26 Aug 2017 15:19:21 +0100 Subject: [PATCH 1/2] implement isolation of UserCreator widget with nested stores --- src/App.js | 16 ++++++++++----- src/UserCreator/index.js | 42 +++++++++++++++++++++++++++++++++++--- src/UserCreator/reducer.js | 8 ++++++-- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/App.js b/src/App.js index b5171a5..88c69aa 100644 --- a/src/App.js +++ b/src/App.js @@ -2,14 +2,16 @@ import React, { Component } from "react"; import UserCreator from "./UserCreator"; import { connect } from "react-redux"; +import { userCreated } from "./state/users"; + class App extends Component { render() { - const { users } = this.props; + const { users, createUser } = this.props; return (
- - - + + +
{users.map(({ name }) =>
@@ -28,4 +30,8 @@ function mapStateToProps(state) { }; } -export default connect(mapStateToProps)(App); +const mapDispatchToProps = { + createUser: userCreated +}; + +export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/src/UserCreator/index.js b/src/UserCreator/index.js index bda7223..1ba0268 100644 --- a/src/UserCreator/index.js +++ b/src/UserCreator/index.js @@ -1,6 +1,30 @@ import React, { Component } from "react"; import { updateNameField, createUser } from "./actions"; -import { connect } from "react-redux"; +import reducer from "./reducer"; + +import { connect, Provider } from "react-redux"; +import { createStore } from "redux"; + +function isolate(WrappedComponent) { + return class Widget extends Component { + constructor(props) { + super(props); + this.displayName = props.id; + this.store = createStore( + reducer, + window.__REDUX_DEVTOOLS_EXTENSION__ && + window.__REDUX_DEVTOOLS_EXTENSION__({ name: props.id }) + ); + } + render() { + return ( + + + + ); + } + }; +} class UserCreator extends Component { updateNameField = event => { @@ -10,6 +34,14 @@ class UserCreator extends Component { event.preventDefault(); this.props.createUser(); }; + + componentWillReceiveProps(nextProps) { + const { createdUser } = nextProps; + if (createdUser && !this.props.createdUser) { + this.props.onUserCreated(createdUser); + } + } + render() { const { name } = this.props; return ( @@ -22,10 +54,14 @@ class UserCreator extends Component { } } -const mapStateToProps = state => ({ name: state.name }); +const mapStateToProps = state => ({ + name: state.name, + createdUser: state.createdUser +}); + const mapDispatchToProps = { updateNameField: updateNameField, createUser: createUser }; -export default connect(mapStateToProps, mapDispatchToProps)(UserCreator); +export default isolate(connect(mapStateToProps, mapDispatchToProps)(UserCreator)); diff --git a/src/UserCreator/reducer.js b/src/UserCreator/reducer.js index 36c77be..acefe4c 100644 --- a/src/UserCreator/reducer.js +++ b/src/UserCreator/reducer.js @@ -1,13 +1,17 @@ -import { UPDATE_NAME_FIELD } from "./actions"; +import { UPDATE_NAME_FIELD, CREATE_USER } from "./actions"; const initialState = { - name: "" + name: "", + // Only way of getting the information back to the component.. + createdUser: null }; export default function(state = initialState, action) { switch (action.type) { case UPDATE_NAME_FIELD: return { ...state, name: action.payload }; + case CREATE_USER: + return { ...state, createdUser: { name: state.name } }; default: return state; } From 41c5f782559d45bf8eb9258564afbbc8e478a989 Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Sat, 11 Nov 2017 17:07:41 +0000 Subject: [PATCH 2/2] create bridge for mapping actions to props --- src/UserCreator/container.js | 36 +++++++++++++++++++ src/UserCreator/index.js | 69 ++++-------------------------------- src/UserCreator/reducer.js | 6 ++-- src/utils/Isolate.js | 46 ++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 66 deletions(-) create mode 100644 src/UserCreator/container.js create mode 100644 src/utils/Isolate.js diff --git a/src/UserCreator/container.js b/src/UserCreator/container.js new file mode 100644 index 0000000..68ed311 --- /dev/null +++ b/src/UserCreator/container.js @@ -0,0 +1,36 @@ +import React, { Component } from "react"; +import { updateNameField, createUser } from "./actions"; +import { connect } from "react-redux"; + +class UserCreator extends Component { + updateNameField = event => { + this.props.updateNameField(event.target.value); + }; + createUser = event => { + event.preventDefault(); + this.props.createUser(); + }; + + render() { + const { name } = this.props; + return ( +
+

User creator

+ + +
+ ); + } +} + +const mapStateToProps = state => ({ + name: state.name, + createdUser: state.createdUser +}); + +const mapDispatchToProps = { + updateNameField: updateNameField, + createUser: createUser +}; + +export default connect(mapStateToProps, mapDispatchToProps)(UserCreator); diff --git a/src/UserCreator/index.js b/src/UserCreator/index.js index 1ba0268..575c7a6 100644 --- a/src/UserCreator/index.js +++ b/src/UserCreator/index.js @@ -1,67 +1,12 @@ -import React, { Component } from "react"; -import { updateNameField, createUser } from "./actions"; import reducer from "./reducer"; +import isolate from "../utils/Isolate"; +import UserCreatorContainer from "./container"; +import { CREATE_USER } from "./actions"; -import { connect, Provider } from "react-redux"; -import { createStore } from "redux"; - -function isolate(WrappedComponent) { - return class Widget extends Component { - constructor(props) { - super(props); - this.displayName = props.id; - this.store = createStore( - reducer, - window.__REDUX_DEVTOOLS_EXTENSION__ && - window.__REDUX_DEVTOOLS_EXTENSION__({ name: props.id }) - ); - } - render() { - return ( - - - - ); - } +function mapActionsToProps(props) { + return { + [CREATE_USER]: (state, action) => props.onUserCreated({ name: state.name }) }; } -class UserCreator extends Component { - updateNameField = event => { - this.props.updateNameField(event.target.value); - }; - createUser = event => { - event.preventDefault(); - this.props.createUser(); - }; - - componentWillReceiveProps(nextProps) { - const { createdUser } = nextProps; - if (createdUser && !this.props.createdUser) { - this.props.onUserCreated(createdUser); - } - } - - render() { - const { name } = this.props; - return ( -
-

User creator

- - -
- ); - } -} - -const mapStateToProps = state => ({ - name: state.name, - createdUser: state.createdUser -}); - -const mapDispatchToProps = { - updateNameField: updateNameField, - createUser: createUser -}; - -export default isolate(connect(mapStateToProps, mapDispatchToProps)(UserCreator)); +export default isolate(reducer, mapActionsToProps)(UserCreatorContainer); diff --git a/src/UserCreator/reducer.js b/src/UserCreator/reducer.js index acefe4c..2a894ab 100644 --- a/src/UserCreator/reducer.js +++ b/src/UserCreator/reducer.js @@ -1,9 +1,7 @@ import { UPDATE_NAME_FIELD, CREATE_USER } from "./actions"; const initialState = { - name: "", - // Only way of getting the information back to the component.. - createdUser: null + name: "" }; export default function(state = initialState, action) { @@ -11,7 +9,7 @@ export default function(state = initialState, action) { case UPDATE_NAME_FIELD: return { ...state, name: action.payload }; case CREATE_USER: - return { ...state, createdUser: { name: state.name } }; + return state; default: return state; } diff --git a/src/utils/Isolate.js b/src/utils/Isolate.js new file mode 100644 index 0000000..7ab7c01 --- /dev/null +++ b/src/utils/Isolate.js @@ -0,0 +1,46 @@ +import React, { Component } from "react"; +import { Provider } from "react-redux"; +import { createStore, compose, applyMiddleware } from "redux"; + +export default function isolate(reducer, mapActionToProps) { + return WrappedComponent => { + return class Widget extends Component { + constructor(props) { + super(props); + this.displayName = props.id; + + const composeEnhancers = + typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ + name: this.displayName + }) + : compose; + + this.store = createStore( + reducer, + composeEnhancers( + applyMiddleware(store => next => action => { + const result = next(action); + const state = store.getState(); + + const mappings = mapActionToProps(this.props); + + if (mappings[action.type]) { + mappings[action.type](state, action); + } + + return result; + }) + ) + ); + } + render() { + return ( + + + + ); + } + }; + }; +}