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;
Related
I am now implementing the react-bootstrap Accordion with the Custom Toggle with the reference below.
https://react-bootstrap.github.io/components/accordion/
But if you have the componentDidUpdate() in your code, Accordion does not work.
You click the Accordion's Custom Toggle.
The Accordion Collapse expands.
But the componentDidUpdate() or componentDidMount() is kicked and it updates the screen.
They extract data from the server by using fetch.
This seems to be an issue.
The just expanded Accordion Collapse is immediately folded.
So you cannot expand the Accordion.
Anyone can provide me with any solution?
The entire code is as below.
import React from 'react';
import {Accordion, Card, useAccordionToggle, ListGroup} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
class EMP extends React.Component {
constructor(props) {
super(props);
this.state = {employees: []}
}
componentDidMount() {
fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
componentDidUpdate() {
fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
render() {
return (
<div>
<EmployeeList employees={this.state.employees} />
</div>
)
}
}
class EmployeeList extends React.Component {
constructor(props) {
super(props);
}
render() {
const CustomToggle = ({ children, eventKey }) => {
const decoratedOnClick = useAccordionToggle(
eventKey,
(e) =>{
var item = e.target.parentNode.children[0];
if(item.innerText.includes('▶',0)){
item.innerText = item.innerText.replace('▶', '▼');
}
else{
item.innerText = item.innerText.replace('▼', '▶');
}
}
);
return (
<ListGroup.Item
onClick={decoratedOnClick}
style={{cursor: 'pointer', paddingBottom: '0', paddingTop: '0' }}
>
{children}
</ListGroup.Item>
);
}
return (
<Accordion defaultActiveKey='0'>
<Card>
<Card.Header>
<CustomToggle eventKey='0'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='0'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
<Card>
<Card.Header>
<CustomToggle eventKey='1'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='1'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
}
export default EMP;
I found the reason of the issue.
With the content of componentDidMount() and componentDidUpdate() without arrow function ( () => fetch() ), the app falls into an infinite loop.
render() is called.
this.setState() is called.
render() is called again.
To prevent this inifinite loop, you must write the arrow function in the componentDidmount().
So the correct and complete code is as below.
import React from 'react';
import {Accordion, Card, useAccordionToggle, ListGroup} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
class EMP extends React.Component {
constructor(props) {
super(props);
this.state = {employees: []}
}
componentDidMount() {
() => fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
componentDidUpdate() {
() => fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
render() {
return (
<div>
<EmployeeList employees={this.state.employees} />
</div>
)
}
}
class EmployeeList extends React.Component {
constructor(props) {
super(props);
}
render() {
const CustomToggle = ({ children, eventKey }) => {
const decoratedOnClick = useAccordionToggle(
eventKey,
(e) =>{
var item = e.target.parentNode.children[0];
if(item.innerText.includes('▶',0)){
item.innerText = item.innerText.replace('▶', '▼');
}
else{
item.innerText = item.innerText.replace('▼', '▶');
}
}
);
return (
<ListGroup.Item
onClick={decoratedOnClick}
style={{cursor: 'pointer', paddingBottom: '0', paddingTop: '0' }}
>
{children}
</ListGroup.Item>
);
}
return (
<Accordion defaultActiveKey='0'>
<Card>
<Card.Header>
<CustomToggle eventKey='0'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='0'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
<Card>
<Card.Header>
<CustomToggle eventKey='1'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='1'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
}
export default EMP;
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>
)
}
}
I'm trying to create a header component that changes based on which screen you're viewing. However, the child component is not working if I try to create some dynamic buttons.
I have split each part of the header into smaller components, the problem is that the right side of the header HeaderRightSide is not rendering unless I add each button myself.
const screenIcon = {
Home: 'home',
Settings: 'tools'
}
export class AppHeader extends Component {
static propTypes = {
screenName: PropTypes.string.isRequired,
navigation: PropTypes.object.isRequired,
googleUser: PropTypes.object.isRequired,
UserSignIn_logOut: PropTypes.func.isRequired
}
signOut = async () => {
try {
await GoogleSignin.revokeAccess();
await GoogleSignin.signOut();
} catch (error) {
// console.error(error);
} finally {
this.props.UserSignIn_logOut({});
this.props.navigation.navigate('SignIn');
}
}
componentDidMount() {
console.log(this.props.navigation.state)
}
render() {
return (
<Header>
<HeaderLeftSide screenName={this.props.screenName} />
<HeaderBody screenName={this.props.screenName} />
<HeaderRightSide screenName={this.props.screenName} navigation={this.props.navigation} />
</Header>
)
}
}
HeaderLeftSide = (props) => (
<Left>
<Button transparent>
<Icon name={screenIcon[props.screenName]} />
</Button>
</Left>
)
HeaderBody = (props) => (
<Body>
<Title>{props.screenName}</Title>
</Body>
)
HeaderRightSide = (props) => (
<Right>
{Object.keys(screenIcon).forEach(screen =>
( <Button transparent>
<Icon name={screenIcon[screen]} />
</Button>)
)}
</Right>
)
const mapStateToProps = (state) => ({
googleUser: state.UserSignInReducer.googleUser
})
const mapDispatchToProps = {
UserSignIn_logOut: () => {
dispatch(UserSignIn_logOut());
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AppHeader)
I just had to change forEach to map. Correct code:
HeaderRightSide = (props) => (
<Right>
{Object.keys(screenIcon).map(screen => (
<Button transparent>
<Icon name={screenIcon[screen]} />
</Button>
)
)}
</Right>
)
I have this component:
class DashboardPage extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
shownPage: ActiveDeals,
error: false,
errorDetails: null,
activeIcon: "Home"
};
}
componentDidMount() {
//
}
setShownPage = (name, iconName) => () => {
this.setState({ shownPage: name, activeIcon: iconName });
};
getIconColor = () => {
// could I call this from the Home component and check its name? Or know the caller?
return "primary";
};
render() {
const { classes } = this.props;
const menuItems = (
<List>
<ListItem className={classNames(classes.listItem)} button onClick={this.setShownPage(ActiveDeals, "Home")}>
<ListItemIcon className={classNames(classes.listItemIcon)}>
<Home color={this.state.activeIcon === "Home" ? "primary" : "secondary"} />
</ListItemIcon>
</ListItem>
<ListItem className={classNames(classes.listItem)} button onClick={this.setShownPage(UpcomingDates, "CalendarToday")}>
<ListItemIcon className={classNames(classes.listItemIcon)}>
<CalendarToday color={this.state.activeIcon === "CalendarToday" ? "primary" : "secondary"} />
</ListItemIcon>
</ListItem>
<ListItem className={classNames(classes.listItem)} button onClick={this.setShownPage(DealsPipeline, "FilterList")}>
<ListItemIcon className={classNames(classes.listItemIcon)}>
<FilterList color={this.state.activeIcon === "FilterList" ? "primary" : "secondary"} />
</ListItemIcon>
</ListItem>
</List>
);
return (
<MainFrame
route={this.props.match.url}
title={this.state.shownPage.title}
menuItems={menuItems}
open={this.state.open}
topRightFeature={this.state.shownPage.topRightFeature}
>
<this.state.shownPage />
<div>Primary color is {this.props.theme.palette.primary.main}</div>
</MainFrame>
);
}
}
export default withStyles(styles, { withTheme: true })(DashboardPage);
... I'm used to using nameof() and type() in backend languages to know the name a given instance. In React, I have yet to find a way to do this.
Instead of setting the icon colors based on state (which uses hardcoded strings, yuck), I want a functional way to either traverse the dom tree to find relative children, or a way to just know the name of the icon which calls the getIconColor method so I can compare it to the active state.
Is there any way for a component to set a property while using a function that "knows" that it was called from e.g. Home?
I think you're trying to solve this in a way that misses out on the declarative power of React and the possibilities that component composition provides. The code duplication between the list items is begging for another component to be introduced:
const listItemStyles = {
listItem: {
/* whatever styles you need */
},
listItemIcon: {
/* whatever styles you need */
}
};
const DashboardListItem = withStyles(listItemStyles)(
({ Page, Icon, ShownPage, classes, setShownPage }) => {
return (
<ListItem
className={classes.listItem}
button
onClick={() => setShownPage(Page)}
>
<ListItemIcon className={classes.listItemIcon}>
<Icon color={ShownPage === Page ? "primary" : "secondary"} />
</ListItemIcon>
</ListItem>
);
}
);
Then your menuItems becomes:
const menuItems = [
{ Page: ActiveDeals, Icon: Home },
{ Page: UpcomingDates, Icon: CalendarToday },
{ Page: DealsPipeline, Icon: FilterList }
];
const mappedMenuItems = menuItems.map((menuItem, index) => (
<DashboardListItem
key={index}
{...menuItem}
ShownPage={this.state.shownPage}
setShownPage={this.setShownPage}
/>
));
With the full code looking like this:
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import { withStyles } from "#material-ui/core/styles";
import Home from "#material-ui/icons/Home";
import CalendarToday from "#material-ui/icons/CalendarToday";
import FilterList from "#material-ui/icons/FilterList";
const styles = {};
const ActiveDeals = () => {
return <div>ActiveDeals Page!</div>;
};
const UpcomingDates = () => {
return <div>UpcomingDates Page!</div>;
};
const DealsPipeline = () => {
return <div>DealsPipeline Page!</div>;
};
const listItemStyles = {
listItem: {
/* whatever styles you need */
},
listItemIcon: {
/* whatever styles you need */
}
};
const DashboardListItem = withStyles(listItemStyles)(
({ Page, Icon, ShownPage, classes, setShownPage }) => {
return (
<ListItem
className={classes.listItem}
button
onClick={() => setShownPage(Page)}
>
<ListItemIcon className={classes.listItemIcon}>
<Icon color={ShownPage === Page ? "primary" : "secondary"} />
</ListItemIcon>
</ListItem>
);
}
);
const menuItems = [
{ Page: ActiveDeals, Icon: Home },
{ Page: UpcomingDates, Icon: CalendarToday },
{ Page: DealsPipeline, Icon: FilterList }
];
class DashboardPage extends Component {
constructor(props) {
super(props);
this.state = {
shownPage: ActiveDeals
};
}
setShownPage = page => {
this.setState({ shownPage: page });
};
render() {
const mappedMenuItems = menuItems.map((menuItem, index) => (
<DashboardListItem
key={index}
{...menuItem}
ShownPage={this.state.shownPage}
setShownPage={this.setShownPage}
/>
));
return (
<div>
<List>{mappedMenuItems}</List>
<this.state.shownPage />
<div>Primary color is {this.props.theme.palette.primary.main}</div>
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(DashboardPage);
Here's a working example:
You can get the identity of the caller if you bind the function in the component where it is used. This would work only in a class component.
Something like this:
class Apple extends React.Component {
constructor(props) {
super(props);
this.getName = props.getName.bind(this);
}
render() {
return <div>I am an {this.getName()}</div>;
}
}
class Banana extends React.Component {
getName() {
return this.constructor.name;
}
render() {
return (
<div className="App">
<Apple getName={this.getName} />
</div>
);
}
}
https://codesandbox.io/s/247lpxl4j0
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 !