Last active
November 6, 2018 21:09
-
-
Save byelipk/08a15d93bf808072b4ed43203fa36ff3 to your computer and use it in GitHub Desktop.
Example of a state reducer pattern in React. Consumer has pretty much full control over UI and state management.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args)); | |
class Chat extends React.Component { | |
static defaultProps = { | |
handleClick: () => console.log("Let's Chat"), | |
stateReducer: (state, changes) => changes | |
}; | |
initialMood = "Curious"; | |
initialState = { | |
mood: this.initialMood | |
}; | |
state = this.initialState; | |
// Actions | |
handleClick = () => { | |
this.internalSetState(state => state, this.props.handleClick); | |
}; | |
handleReset = () => { | |
this.internalSetState(this.initialState); | |
}; | |
// Allow consumers of this component to update our state | |
internalSetState(changes, callback) { | |
this.setState(state => { | |
// handle function setState call | |
const changesObject = | |
typeof changes === "function" ? changes(state) : changes; | |
// apply state reducer | |
const reducedChanges = | |
this.props.stateReducer(state, changesObject) || {}; | |
// return null if there are no changes to be made | |
// (to avoid an unecessary rerender) | |
return Object.keys(reducedChanges).length ? reducedChanges : null; | |
}, callback); | |
} | |
// Props Helpers | |
getButtonProps = ({ onClick, ...props }) => ({ | |
onClick: callAll(onClick, this.handleClick), | |
type: "button", | |
className: "btn btn-funny", | |
...props | |
}); | |
getResetProps = ({ onClick, ...props }) => ({ | |
onClick: callAll(onClick, this.handleReset), | |
type: "button", | |
className: "btn btn-reset", | |
...props | |
}); | |
mapStateAndHelpers = () => { | |
return { | |
mood: this.state.mood, | |
getButtonProps: this.getButtonProps, | |
getResetProps: this.getResetProps | |
}; | |
}; | |
render() { | |
return ( | |
<div className="WTF"> | |
{this.props.children(this.mapStateAndHelpers())} | |
</div> | |
); | |
} | |
} | |
class FunnyChat extends React.Component { | |
initialClickState = { | |
isCurious: false, | |
isHappy: false, | |
isSilly: false | |
}; | |
initialState = { | |
value: 0, | |
...this.initialClickState | |
}; | |
state = this.initialState; | |
handleClick = () => { | |
this.setState({ | |
value: this.state.value + 1 | |
}); | |
}; | |
handleHappyClick = () => { | |
this.setState(({ isHappy }) => ({ | |
...this.initialClickState, | |
isHappy: !isHappy | |
})); | |
}; | |
handleCuriousClick = () => { | |
this.setState(({ isCurious }) => ({ | |
...this.initialClickState, | |
isCurious: !isCurious | |
})); | |
}; | |
handleSillyClick = () => { | |
this.setState(({ isSilly }) => ({ | |
...this.initialClickState, | |
isSilly: !isSilly | |
})); | |
}; | |
handleReset = () => { | |
this.setState(this.initialState); | |
}; | |
// A type of "hook" we can use to update Chat's internal state | |
toggleStateReducer = (state, changes) => { | |
if (this.state.isCurious) { | |
return { ...changes, mood: "Curious" }; | |
} | |
else if (this.state.isHappy) { | |
return { ...changes, mood: "Happy" }; | |
} | |
else if (this.state.isSilly) { | |
return { ...changes, mood: "Silly" }; | |
} | |
else if (this.state.value > 5 && this.state.value < 10) { | |
return { ...changes, mood: "Happy" }; | |
} | |
else if (this.state.value > 10) { | |
return { ...changes, mood: "Silly" }; | |
} | |
else { | |
return changes; | |
} | |
}; | |
render() { | |
return ( | |
<Chat stateReducer={this.toggleStateReducer}> | |
{({ mood, getButtonProps, getResetProps }) => ( | |
<div className="funny-chat"> | |
<button | |
{...getButtonProps({ | |
onClick: this.handleClick | |
})} | |
> | |
{`Click this ${mood} Button - ${this.state.value}`} | |
</button> | |
<hr /> | |
<button | |
{...getButtonProps({ | |
onClick: this.handleHappyClick | |
})} | |
> | |
Be Happy | |
</button> | |
<button | |
{...getButtonProps({ | |
onClick: this.handleCuriousClick | |
})} | |
> | |
Be Curious | |
</button> | |
<button | |
{...getButtonProps({ | |
onClick: this.handleSillyClick | |
})} | |
> | |
Be Silly | |
</button> | |
<button | |
{...getResetProps({ | |
onClick: this.handleReset | |
})} | |
> | |
Start Over | |
</button> | |
</div> | |
)} | |
</Chat> | |
); | |
} | |
} | |
class App extends React.Component { | |
render() { | |
return <FunnyChat />; | |
} | |
} | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment