Skip to content

Instantly share code, notes, and snippets.

@Thivieira
Created March 2, 2018 23:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Thivieira/d8c71bb5d0f5e9de0429c83bba761934 to your computer and use it in GitHub Desktop.
Save Thivieira/d8c71bb5d0f5e9de0429c83bba761934 to your computer and use it in GitHub Desktop.
React Redux modals with portals(3/3)
<!-- What our app will be rendered into -->
<div id="app"></div>
const { applyMiddleware, createStore, combineReducers, bindActionCreators } = Redux;
const { Provider, connect } = ReactRedux;
const { render } = ReactDOM;
const ReactDOM = ReactDOM;
const { Component } = React;
/* --- CONSTANTS --- */
const OPEN_MODAL = 'OPEN_MODAL';
const CLOSE_MODAL = 'CLOSE_MODAL';
/* --- REDUCERS --- */
const initialState = {
modals: [],
}
function reducer(state = initialState, action) {
switch (action.type) {
case OPEN_MODAL:
return {
...state,
modals: state.modals.concat(action.obj)
};
case CLOSE_MODAL:
return {
...state,
modals: state.modals.filter(item => item.id !== action.obj.id),
};
default:
return state;
}
};
/* --- ACTIONS --- */
const openModal = (obj) => {
return {
type: OPEN_MODAL,
obj,
}
}
const closeModal = (obj) => {
return {
type: CLOSE_MODAL,
obj,
}
}
/* --- COMPONENTS --- */
class MyPortal extends React.PureComponent {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
document.body.appendChild(this.el);
}
componentWillUnmount() {
document.body.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(this.props.children, this.el);
}
}
class Modal extends Component {
onClose(){
if(this.props.item.onClose){
this.props.item.onClose();
this.props.onClose(this.props.item);
} else {
this.props.onClose(this.props.item);
}
}
onConfirm(){
if(this.props.item.onConfirm){
this.props.item.onConfirm();
this.props.onClose(this.props.item);
}
}
render() {
const { type } = this.props.item;
if (type === 'confirmation') {
const { text } = this.props.item;
return (
<div className="modal-wrapper">
<div className="modal">
<div className="text">{text}</div>
<div className="buttons">
<button className="modal-button" onClick={() => this.onConfirm()}>Confirm</button>
<button className="modal-button" onClick={() => this.onClose()}>Close</button>
</div>
</div>
</div>
)
} else if (type === 'custom') {
const { content } = this.props.item;
return (
<div className="modal-wrapper">
<div className="modal">
<button className="close" onClick={() => this.onClose()}>&times;</button>
{content}
</div>
</div>
)
}
return (
<div></div>
);
}
}
class Modals extends Component {
render() {
const modals = this.props.modals.map((item,i) => <MyPortal key={i} ><Modal item={item} onClose={(item) => this.props.dispatch(closeModal(item))}/></MyPortal>)
return (
<div className="modals">
{modals}
</div>
);
}
}
const ModalContainer = connect(
function mapStateToProps(state) {
return {
modals: state.modals
};
},
function mapDispatchToProps(dispatch) {
return {
dispatch
}
}
)(Modals);
class CustomModalContent extends Component {
render() {
return (
<div className="modal-content">Custom Modal Content</div>
)
}
}
class App extends Component {
render() {
return (
<div className="App">
<button className="test-button" onClick={() => this.props.dispatch(openModal({
id: uuid.v4(),
type: 'confirmation',
text: 'Are you sure to do this?',
onClose: () => console.log("fire at closing event"),
onConfirm: () => console.log("fire at confirming event"),
}))}>Open confirmation modal</button>
<button className="test-button" onClick={() => this.props.dispatch(openModal({
id: uuid.v4(),
type: 'custom',
content: <CustomModalContent />
}))}>Open custom modal</button>
<ModalContainer />
</div>
);
}
}
const AppContainer = connect(
null,
function mapDispatchToProps(dispatch) {
return {
dispatch,
}
}
)(App);
/* --- OTHER --- */
/* --- STORE --- */
const store = createStore(reducer);
// Render the app
render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.7/uuid.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.2.0/redux-thunk.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.min.js"></script>
<script src="https://unpkg.com/react@16.0.0-rc.3/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.0.0-rc.3/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.min.js"></script>
.test-button{
font-size: 20px;
color: gray;
padding: 5px;
margin: 20px;
}
.modals {
position: fixed;
bottom:0;
left:0;
right:0;
height: 300px;
}
.modal-wrapper{
position: absolute;
bottom:0;
left:0;
right:0;
top: 0;
background: rgba(0,0,0,0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
width: 200px;
background: #fff;
border-radius: 5px;
min-width: 500px;
position: relative;
}
.text {
font-weight: bold;
font-size: 20px;
margin: 20px 0;
text-align: center;
}
.buttons {
margin: 10px 0;
display: flex;
justify-content: space-around;
}
.modal-button {
font-size: 15px;
color: gray;
padding: 5px;
border-radius: 5px;
}
.close {
position: absolute;
top: 10px;
right: 0px;
transform: translateY(-50%);
background-color: transparent;
border: none;
color: #fff;
font-size: 2em;
cursor: pointer;
}
.modal-content {
background: rgba(193, 29, 29,0.5);
color: #fff;
height: 150px;
display: flex;
justify-content: center;
align-items: center;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.2.3/foundation.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment