How to trigger react-modal from the layout container? - reactjs

I'm working to use react-modal in my React+Redux+ReactRouter4 App.
I have a MainLayout container and a Home container.
The modal will only be used when the home container is rendered so I have ReactModal's logic inside the Home Container. And I can easily open the modal from the Home Container like so:
<button onClick={this.openModal}>Open Modal</button>
The problem is the MainLayout container has a navigation that also needs the ability to open the modal, but obviously, this.openModal does not exist there... How can I allow the MainLayout Container to open the modal in the Home container?
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
modalIsOpen: false
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({modalIsOpen: true});
}
closeModal() {
this.setState({modalIsOpen: false});
}
render() {
return (
<div>
....
<button onClick={this.openModal}>Open Modal</button>
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
style={modalCustomStyles}
contentLabel="Example Modal"
>
<h2 ref={subtitle => this.subtitle = subtitle}>Hi</h2>
<button onClick={this.closeModal}>close</button>
<div>I am a modal</div>
</Modal>
</div>
)
};
};
App.jsx
const WithMainLayout = ({component: Component, ...more}) => {
return <Route {...more} render={props => {
return (
<MainLayout {...props}>
<Component {...props} />
</MainLayout>
);
}}/>;
};
....
<WithMainLayout exact path="/" component={Home} />

What I would do is just move the modalOpenState into redux rather than keeping it in local state. Your initial state would be like this.
export default {
modalIsOpen: false
};
Then write an action to toggle the modal state in the store.
export function toggleQuestionModal(isOpen) {
return { type: types.TOGGLE_QUESTION_MODAL, payload: isOpen };
}
Your presentational component for modal should be something like this.
import React, { Component, PropTypes } from 'react';
import Modal from 'react-modal';
const QuestionModal = ({ modalIsOpen, openModal, closeModal, afterOpenModal }) => {
const customStyles = {
overlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.75)'
},
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
height: '50%',
width: '80%',
transform: 'translate(-50%, -50%)'
}
};
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal
isOpen={modalIsOpen}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Create A Question"
role="dialog"
>
<h2>Hello</h2>
<button onClick={closeModal}>close</button>
<div>I am a modal</div>
<form>
<input />
<button>tab navigation</button>
<button>stays</button>
<button>inside</button>
<button>the modal</button>
</form>
</Modal>
</div>
);
};
QuestionModal.propTypes = {
modalIsOpen: PropTypes.bool.isRequired,
openModal: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
afterOpenModal: PropTypes.func.isRequired
};
export default QuestionModal;
Finally here's your container component for the modal.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { toggleQuestionModal, toggleConfirmation } from '../actions/questionActions';
import QuestionModal from '../components/questionModal';
class QuestionPage extends Component {
constructor(props, context) {
super(props, context);
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.afterOpenModal = this.afterOpenModal.bind(this);
}
openModal() {
this.props.toggleQuestionModal(true);
}
afterOpenModal() {
// references are now sync'd and can be accessed.
// this.subtitle.style.color = '#f00';
}
closeModal() {
this.props.toggleConfirmation(true);
}
render() {
const { modalIsOpen } = this.props;
return (
<div>
<QuestionModal modalIsOpen={modalIsOpen} openModal={this.openModal} closeModal={this.closeModal}
afterOpenModal={this.afterOpenModal} />
</div>
);
}
}
QuestionPage.propTypes = {
modalIsOpen: PropTypes.bool.isRequired,
toggleQuestionModal: PropTypes.func.isRequired,
};
function mapStateToProps(state, ownProps) {
return {
modalIsOpen: state.question.modalIsOpen
};
}
function mapDispatchToProps(dispatch) {
return {
toggleQuestionModal: bindActionCreators(toggleQuestionModal, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(QuestionPage);
When you want to open the modal from any component merely invoke the toggleQuestionModal action with a true value. This will change the state and render the modal. Redux recommends to keep everything in the state. I do practice that. Don't keep things local. Keeping everything in state makes it easier for you to do a time travel debug using tools. You may find sample implementation here. Hope this helps. Happy Coding !

Related

React, problem with passing state as props

So I am quite new to React world, and I have this problem I am trying to solve, but I don't quite understand why it is happening.
So I want to pass the state of component to parent component and from parent component to child component and everything look okay, and in console log the state goes trough, but nothing changes. I believe there is a way I need to listen for state change or something within child component so it works. If I put true in the parent component, child component also get's true, but if I toggle it on click, it goes trough but nothing changes in the child component.
Also I understand my code is little rough right now ill reafactor it later, but right now I am trying to understand why it does not work.
If anyone could help me I would be thankful for it.
This is component that controls the state.. So the state passes from TurnOnBtn to App and from App it goes to TodoList
import "./Todo.css";
class TurnOnBtn extends Component {
constructor(props) {
super(props);
this.state = { display: false };
this.handleState = this.handleState.bind(this);
}
handleState() {
this.setState({ display: !this.state.display });
this.props.checkDisplay(this.state.display);
}
render() {
return (
<button onClick={this.handleState} className="TurnOnBtn">
<i className="fa fa-power-off"></i>
</button>
);
}
}
export default TurnOnBtn;
parent component App
import TurnOnBtn from "./TurnOnBtn";
import TheMatrix from "./TheMatrxHasYou";
import TodoList from "./TodoList";
import { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = { display: true };
this.checkDisplay = this.checkDisplay.bind(this);
}
checkDisplay(newDisplay) {
this.setState({
display: newDisplay,
});
console.log(this.state);
}
render() {
return (
<div className="App">
<TodoList display={this.state.display} />
<TheMatrix />
<TurnOnBtn checkDisplay={this.checkDisplay} />
</div>
);
}
}
export default App;
child component TodoList
import Todo from "./Todo";
import NewTodoForm from "./NewTodoForm";
import { v4 as uuid } from "uuid";
import "./Todo.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: [],
displayOn: this.props.display,
};
this.newTodo = this.newTodo.bind(this);
this.editTodo = this.editTodo.bind(this);
this.deleteTodo = this.deleteTodo.bind(this);
}
editTodo(id, updatedTask) {
const updatedTodo = this.state.todos.map((todo) => {
if (todo.id === id) {
return { ...todo, todo: updatedTask };
}
return todo;
});
this.setState({
todos: updatedTodo,
});
console.log(updatedTask);
}
deleteTodo(id) {
this.setState({
todos: this.state.todos.filter((todo) => todo.id !== id),
});
}
newTodo(newState) {
this.setState({
todos: [...this.state.todos, { ...newState }],
});
}
render() {
return (
<div
style={this.state.displayOn ? { opacity: 1 } : { opacity: 0 }}
className="Todo-screen"
>
{" "}
<div className="TodoList">
<div className="TodoList-todos">
{" "}
{this.state.todos.map((todo) => (
<Todo
key={uuid()}
id={todo.id}
active={todo.active}
editTodo={this.editTodo}
deleteTodo={this.deleteTodo}
todoItem={todo.todo}
/>
))}
</div>
</div>{" "}
<NewTodoForm newTodo={this.newTodo} />
</div>
);
}
}
export default TodoList;
The bug here is in these line of codes:
handleState() {
this.setState({ display: !this.state.display });
this.props.checkDisplay(this.state.display);
}
Remember setState is an async function, so by the time you set a new state using setState, the value for this.state is not guaranteed changed.
One way to fix this is using the setState callback, which will run after the state is changed:
handleState() {
this.setState({ display: !this.state.display }, function() {
this.props.checkDisplay(this.state.display);
});
}
But you don't need to use another state to keep display state in TurnOnBtn as you can pass the toggle callback from the parent:
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = { display: true };
this.toggleDisplay = this.toggleDisplay.bind(this);
}
toggleDisplay() {
this.setState({
display: !this.state.display,
});
}
render() {
return (
<div className="App">
<TodoList display={this.state.display} />
<TheMatrix />
<TurnOnBtn toggleDisplay={this.toggleDisplay} />
</div>
);
}
}
TurnOnBtn.js
class TurnOnBtn extends Component {
constructor(props) {
super(props);
this.handleState = this.handleState.bind(this);
}
handleState() {
this.props.toggleDisplay();
}
render() {
return (
<button onClick={this.handleState} className="TurnOnBtn">
<i className="fa fa-power-off"></i>
</button>
);
}
}

open a html modal programmatically in React

I have this two elements a button and a dialog
<dialog className='w-11/12 shadow-none rounded-tl-md rounded-tr-md lg:rounded-lg absolute'>wqdwe</dialog>
<button className=" px-6 py-2 rounded absolute mt-12 ml-12" onClick={} >Click</button>
How can I open the dialog on clicking the button in React
constructor(props) {
super(props);
this.myRef = React.createRef();
}
showModals(){
this.myRef.showModal();
}
componentDidMount() {
//this.showModals()
}
EDIT: I am trying to access the .showModal() method in the dialog according to MDN https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog. How can I do that, I need the dimmed background feature when the modal is opened.
You do not need componentDidMount nor useRef with the state and using the props open of the dialog you can show it conditionally.
first solution using isOpen is the state
class Modal extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<dialog style={{width: "80%", height: "80%", marginTop: 10, backgroundColor: '#eee'}}
open={this.props.open}
>
<p>Greetings, one and all!</p>
</dialog>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
isOpen: false
};
}
switchModal = (prevState) => {
this.setState((prevState, props) => {
return { isOpen: !prevState.isOpen }
});
}
render() {
return (
<div>
<button onClick={this.switchModal}>
{this.state.isOpen ? 'Close' : 'Open'} Modal
</button>
<br/>
<Modal open={this.state.isOpen}/>
</div>
);
}
}
React.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.0/react.min.js"></script>
<div id="root"></div>
second solution using native showModal method. With this method you can use the css property dialog::backdrop.
class Modal extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<dialog id='modal' style={{width: "80%", height: "80%", marginTop: 10, backgroundColor: '#eee'}}
>
<p>Greetings, one and all!</p>
<button onClick={this.props.closeModal}>
Close Modal
</button>
</dialog>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
isOpen: false
};
}
switchModal = (prevState) => {
this.setState((prevState, props) => {
if(!prevState.isOpen) {
document.getElementById('modal').showModal()
} else {
document.getElementById('modal').close()
}
return { isOpen: !prevState.isOpen }
});
}
render() {
return (
<div>
{!this.state.isOpen && <button onClick={this.switchModal}>
Open Modal
</button>}
<br/>
<Modal
closeModal={this.switchModal}
/>
</div>
);
}
}
React.render(<App />, document.getElementById('root'));
dialog {
height: 80%;
width: 80%
}
dialog::backdrop {
background: rgba(255,0,0,.25);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.0/react.min.js"></script>
<div id="root"></div>
You can use the React state API to show and hide components based on actions taken elsewhere in the code.
class MyComponent extends React.Component {
constructor() {
this.state = {
isDialogVisible: false
}
}
handleClick = () => {
this.setState({ isDialogVisible: !this.state.isDialogVisible })
}
render() {
const { isDialogVisible } = this.state
return (
<div>
<Button onClick={this.handleClick}>{isDialogVisible ? 'Hide' : 'Show'} dialog</Button>
{this.state.isDialogVisible && <Dialog />}
</div>
)
}
}

Modal is not working with react js, on click of edit button

I am new here in react js, I want to open modal on click of edit button, but it gives me error 'App' is not defined react/jsx-no-undef, Can anyone please help why i am getting that error ? On click of edit button it is call editTask function, and from that function it call toggleModal()function here i have added my full code here, anyhelp will be really appreciated
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import './modal.js';
class PalladiumHub extends React.Component {
render() {
return (<tr>
<td>{this.props.keyuser}</td>
<td>{this.props.name.name}</td>
<td><button type="button" onClick={(e) => { this.props.editTask(this.props.index) }} >Edit</button><button onClick={(e) => { this.props.deleteTask(this.props.index) }}>Delete</button></td>
</tr>
)
}
} //{} {}
class CallCRUD extends React.Component {
constructor(props) {
super(props);
this.deleteTask = this.deleteTask.bind(this);
this.editTask = this.editTask.bind(this);
this.state = {
error: null,
isLoaded: false,
items: [],
isOpen: false
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
toggleModal() {
return <App openModal = {this.openModal} />;
}
deleteTask(index) {
alert(index);
console.log(index);
//return false;
let tasks = this.state.items;
tasks.splice(index, 1);
this.setState({
items: tasks
})
}
editTask(index) {
this.toggleModal();
console.log(index);
}
render() {
console.log(this.state.items);
return (<table border="1"> <tr><th>ID</th><th>Name</th><th>Action</th></tr> {
this.state.items.map((data, index) => {
//return console.log(data.id);
return <PalladiumHub name={data} keyuser={data.id} index={index} key={index} deleteTask={this.deleteTask} editTask={this.editTask} />
})
}
</table>
);
}
}
ReactDOM.render(
<CallCRUD />, document.getElementById('root')
);
modal.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Modal from 'react-modal';
const customStyles = {
content : {
top : '50%',
left : '50%',
right : 'auto',
bottom : 'auto',
marginRight : '-50%',
transform : 'translate(-50%, -50%)'
}
};
// Make sure to bind modal to your appElement (http://reactcommunity.org/react-modal/accessibility/)
//Modal.setAppElement('#root')
class App extends React.Component {
constructor() {
super();
this.state = {
modalIsOpen: false
};
this.openModal = this.openModal.bind(this);
this.afterOpenModal = this.afterOpenModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({modalIsOpen: true});
}
afterOpenModal() {
// references are now sync'd and can be accessed.
this.subtitle.style.color = '#f00';
}
closeModal() {
this.setState({modalIsOpen: false});
}
render() {
return (
<div>
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel="Example Modal"
>
<h2 ref={subtitle => this.subtitle = subtitle}>Hello</h2>
<button onClick={this.closeModal}>close</button>
<div>I am a modal</div>
<form>
<input />
<button>tab navigation</button>
<button>stays</button>
<button>inside</button>
<button>the modal</button>
</form>
</Modal>
</div>
);
}
}
It looks like you dont have App imported into your PalladiumHub and CallCRUD file. It's just saying that Reacy doesnt know where App is coming from.

React Material-UI dropdown menu not working inside Paper

I've copying the instructions on how to use the Menus in Material-UI docs. I do have the button rendered in my container component that has the Paper component but when I click it, the menu is not showing up. Here's the code:
index.js - Container component
const styles = theme => ({
root: theme.mixins.gutters({
paddingBottom: 16
})
});
class ProvidersPage extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
const { classes, providers } = this.props;
return (
<div>
<Paper className={classes.root} elevation={4}>
<ProviderList providers={providers} />
</Paper>
</div>
);
}
}
ProvidersPage.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
providers: state.providers
};
}
export default connect(
mapStateToProps
)(withStyles(styles)(ProvidersPage));
ProvidersList.js component
const styles = theme => ({
actions: {
color: theme.palette.text.secondary,
},
title: {
flex: '1',
}
});
class ProviderList extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
const { classes, providers } = this.props;
return (
<Fragment>
<Toolbar disableGutters>
<div className={classes.title}>
<Typography variant="title">
Providers
</Typography>
</div>
<div className={classes.actions}>
<ProviderMenu />
</div>
</Toolbar>
</Fragment>
);
}
};
ProviderList.propTypes = {
classes: PropTypes.object.isRequired,
providers: PropTypes.array.isRequired
};
export default withStyles(styles)(ProviderList);
ProviderMenu.js - My menu component that has the menus
class ProviderMenu extends React.Component {
state = {
anchorEl: null,
};
handleClick = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { anchorEl } = this.state;
return (
<div>
<Button
aria-owns={anchorEl ? 'simple-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>My account</MenuItem>
<MenuItem onClick={this.handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
}
export default ProviderMenu;

Component claiming state prop is undefined eventhough it is, Auth

Im trying to log out a user as well as displaying a register/login div or profile section, depending if a user is logged in or not, Im trying to do this on a component that gets render on my main app.
Using ReduxDevTools I can see that the auth state changes when I log in a user or not, but when I try to render the LeftContainer setting auth: state.auth I get auth is undefined,
error:
Cannot read property 'isAuthenticated' of undefined
Warning: Failed prop type: The prop auth is marked as required in LeftContainer, but its value is undefined.
LeftContainerProfile/index.js
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router'
import { connect } from 'react-redux';
import { logout } from '../../containers/SignIn/authActions';
import styled from 'styled-components';
import Logo from '../Logo/Logo';
import SocialLinks from '../SocialLinks/social_links';
import ProfileContainer from './profile';
import LinksContainer from './about_links';
const Wrapper = styled.div`
position: absolute;
top: 250px;
width: 20%;
padding-left: 4%;
padding-right: 4%;
// height: 100vh;
`;
const Button = styled(Link)`
width: 100px;
height: 50px;
background-color: red;
color: white;
margin: 5px;
`;
class LeftContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
logout(e) {
e.preventDefault();
this.props.logout();
}
render() {
const { isAuthenticated } = this.props.auth;
const userLinks = (
<div>
<ProfileContainer />
<Button onClick={this.logout.bind(this)}> LOGOUT </Button>
</div>
);
const guestLinks = (
<div>
<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>
</div>
);
return (
<Wrapper>
{ isAuthenticated ? userLinks : guestLinks }
<LinksContainer />
<SocialLinks />
</Wrapper>
);
}
}
LeftContainer.propTypes = {
auth: React.PropTypes.object.isRequired,
logout: React.PropTypes.func.isRequired
}
function mapStateToProps(state) {
return {
auth: state.auth
};
}
export default connect(mapStateToProps, { logout })(LeftContainer);
Im trying to get the state of auth but i get either undefined or object
here is a picture from reduxdevtools
https://gyazo.com/b189a75e1123cb198412adf93bdd3497
edit 3: When I do this
function mapStateToProps(state) {
console.log(state);
return {
isAuthenticated: state.auth
};
}
I get this
https://gyazo.com/432247229816e4a68a50133834b551c9
maybe this will help people in helping find the solution to my problem
edit 4: I found a solution but Im not sure if this was the correct way of going about this, can someone maybe explain why state.auth was not giving my desire result, is it something in my reducer or something else? here is my solution
class LeftContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
logout(e) {
e.preventDefault();
this.props.logout();
}
render() {
const { isAuthenticated } = this.props;
const userLinks = (
<div>
<ProfileContainer />
<Button onClick={this.logout.bind(this)}> LOGOUT </Button>
</div>
);
const guestLinks = (
<div>
<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>
</div>
);
return (
<Wrapper>
{ isAuthenticated ? userLinks : guestLinks }
<div>
{/*<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>*/}
</div>
<LinksContainer />
<SocialLinks />
</Wrapper>
);
}
}
// LeftContainer.propTypes = {
// auth: React.PropTypes.object.isRequired,
// logout: React.PropTypes.func.isRequired
// }
// LeftContainer.propTypes = {
// isAuthenticated: React.PropTypes.bool.isRequired,
// logout: React.PropTypes.func.isRequired
// }
// LeftContainer.defaultProps = {
// isAuthenticated: true,
// }
function mapStateToProps(state) {
console.log(state._root.entries[3][1].isAuthenticated);
return {
isAuthenticated: state._root.entries[3][1].isAuthenticated
};
}
export default connect(mapStateToProps, { logout })(LeftContainer);
or a much simpler approach (than the one I've listed in the comment):
I treated the isAuthenticated directly from the mapeStateToProps function and made it a bool value for more readiblity. This is less error prone because you don't pass a nested object on props. Also as a fallback I've added a defaultProp value.
Hope this does the trick.
class LeftContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
logout(e) {
e.preventDefault();
this.props.logout();
}
render() {
const { isAuthenticated } = this.props;
const userLinks = (
<div>
<ProfileContainer />
<Button onClick={this.logout.bind(this)}> LOGOUT </Button>
</div>
);
const guestLinks = (
<div>
<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>
</div>
);
return (
<Wrapper>
{ isAuthenticated ? userLinks : guestLinks }
<LinksContainer />
<SocialLinks />
</Wrapper>
);
}
}
LeftContainer.propTypes = {
isAuthenticated: React.PropTypes.bool.isRequired,
logout: React.PropTypes.func.isRequired
}
LeftContainer.defaultProps = {
isAuthenticated: false,
}
function mapStateToProps(state) {
return {
isAuthenticated: state.auth && state.auth.isAuthenticated
};
}
export default connect(mapStateToProps, { logout })(LeftContainer);

Resources