I need to build a drop down menu in a modal popup window with using Material UI
I have drop down menu in my modal window, and as a value I see the last item is "Third" and when I want to select for instance "First" it doesn't work, the menu doesn't select it and still having the last one item as a value in menu
I have 2 files App.js and list.js
App.js code:
import React, { Component } from 'react';
import './App.css';
import {AppButtons} from './components/button'
import {AppList} from './components/list'
import Dialog from 'material-ui/Dialog'
import FlatButton from 'material-ui/FlatButton'
export default class App extends Component {
constructor (props) {
super (props)
this.state = {
isModalOpen: false,
itemsList: [
{name: 'First'},
{name: 'Second'},
{name: 'Third'}
],
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = () => this.setState({this.state.itemsList});
render() {
const actions = [
<FlatButton
label="Save"
primary={true}
onClick={() => this.setState({isModalOpen: false})}
/>,
<FlatButton
label="Cancel"
primary={true}
onClick={() => this.setState({isModalOpen: false})}
/>,
];
return (
<div className="container">
<AppButtons
openModal = {() => this.setState ({isModalOpen: true})}
/>
<Dialog
title="Numbers structure"
actions={actions}
open={this.state.isModalOpen}
onRequestClose={() => this.setState({isModalOpen: true})}
>
<AppList
items = {this.state.itemsList}
/>
</Dialog>
</div>
);
}
}
And list.js code:
import React from 'react'
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
const styles = {
customWidth: {
width: 200,
},
};
export const AppList = (props) => {
return (
<div>
<DropDownMenu
style={styles.customWidth}
onChange={this.handleChange}
>
{props.items.map((item, key) => {
return (
<MenuItem
primaryText = {item.name}
openImmediately={true}
autoWidth={false}/>
)
})
}
</DropDownMenu>
<br />
</div>
)
}
Here is the shot
It's because you aren't passing down your handleChange function as a prop to <AppList/>:
<AppList
items = {this.state.itemsList}
/>
Change it to:
<AppList
items = {this.state.itemsList}
handleChange={this.handleChange}
/>
And then in your AppList component, the Dropdown component needs to use this.props.handleChange instead of this.handleChange:
<DropDownMenu
style={styles.customWidth}
onChange={this.props.handleChange}
>
...
</DropDownMenu>
Related
Can someone help me? I'm creating a component inside React, and I want to make it more accessible using forwardRef. In my case, I'm making a button and I'm using the button's properties, and a few more I've done to make it more dynamic.
This is a summary of my code.
export interface ButtonProps
extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
children?: React.ReactNode;
loading?: boolean;
}
class Button extends React.Component<ButtonProps> {
render() {
const {
...otherProps
} = this.props;
return (
<button {...(otherProps)}></button>
)
}
}
export default Button;
I tried to start something, but right away it gave an error
const ForwardedElement = React.forwardRef<ButtonProps, HTMLButtonElement> (
(props: ButtonProps, ref) => <Button {...props}/>
)
export default ForwardedElement;
I suggest you to use useImperativeHandle hook
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. Let's visualize that with an example.
Here's a component as search bar
import React, {forwardRef, useImperativeHandle, useRef} from "react";
import {Form} from "reactstrap";
const SearchBar = (props, ref) => {
const buttonRef = useRef<any>();
useImperativeHandle(ref, () => ({
getCurrentValue: () => {
return buttonRef.current ? buttonRef.current["value"] : '';
},
setCurrentValue: (value) => {
if (buttonRef.current) {
buttonRef.current["value"] = value;
}
}
}));
return (
<Form className="p-3 w-100" onSubmit={(e) => props.onSubmitHandler(e)}>
<div className="form-group m-0">
<div className="input-group">
<input
type="text"
className="form-control"
placeholder="Search ..."
aria-label="Word to be searched"
ref={buttonRef}
/>
<div className="input-group-append">
<button className="btn btn-primary" type="submit">
<i className="mdi mdi-magnify" />
</button>
</div>
</div>
</div>
</Form>
);
}
export default forwardRef(SearchBar);
This is the header component in which we call our search bar component
import React, {useEffect, useRef, useState} from 'react';
import SearchBar from '../Form/Search/SearchBar';
import Router from 'next/router';
const Header = () => {
const mobileSearchRef = useRef<any>();
const [search, setSearch] = useState<any>(false);
const codeSearchHandler = (e) => {
e.preventDefault();
setSearch(!search);
if (mobileSearchRef.current) {
if (mobileSearchRef.current.getCurrentValue() == '') {
return;
}
}
Router.push({
pathname: '/search',
query: {
searchTerm: mobileSearchRef.current
? mobileSearchRef.current.getCurrentValue()
: ''
},
});
mobileSearchRef.current.setCurrentValue('');
};
return (
<React.Fragment>
<header id="page-topbar">
<div className="navbar-header">
<div className="d-flex">
<div className="dropdown d-inline-block d-lg-none ms-2">
<button
onClick={() => {
setSearch(!search);
}}
type="button"
className="btn header-item noti-icon mt-2"
id="page-header-search-dropdown"
>
<i className="mdi mdi-magnify" />
</button>
<div
className={
search
? 'dropdown-menu dropdown-menu-lg dropdown-menu-end p-0 show'
: 'dropdown-menu dropdown-menu-lg dropdown-menu-end p-0'
}
aria-labelledby="page-header-search-dropdown"
>
<SearchBar
id="headerSearchBar"
ref={mobileSearchRef}
onSubmitHandler={(e) => codeSearchHandler(e)}
/>
</div>
</div>
</div>
</div>
</header>
</React.Fragment>
);
};
export default Header;
If we look at the header component, we can see that we get the input value of search bar component using mobileSearchRef and getCurrentValue method. We can also set its value using setCurrentValue method.
You have to pass the ref aside the spread props:
export interface ButtonProps
extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
children?: React.ReactNode;
loading?: boolean;
ref?: React.RefObject<HTMLButtonElement>
}
class Button extends React.Component<ButtonProps> {
render() {
const {
...otherProps,
ref
} = this.props;
return (
<button {...otherProps} ref={ref}></button>
)
}
}
export default Button;
const ForwardedElement = React.forwardRef<ButtonProps, HTMLButtonElement> (
(props: ButtonProps, ref) => <Button {...props} ref={ref}/>
)
export default ForwardedElement;
now it should work, see this question
I created a react app where I display different video games and the app decides which game to play. I also have a file where I stored the data of video games. The goal I'm trying to achieve is to render the youtube video trailer of the corresponding video game when clicking on a button while using React Hooks. I've been using the npm package react-player. If someone could help, I'd appreciate it.
This is the code for the Video Game component:
import React from 'react';
import { Button, message } from 'antd';
import { Row, Col } from 'antd';
import GameEntry from '../GameEntry';
import Games from '../Games';
function VideoGameSection() {
const chooseGame = () => {
var randomGameTitle = [
'Gears 5',
'Halo',
'Hellblade',
'It takes two',
'A Plague Tale',
'Psychonauts',
'Twelve Minutes',
'Ori',
'Streets of Rage',
'Last of Us',
'Boodborne',
'Geenshin Impact',
'Dragon Ball Z:KAKAROT',
'Ghost Tsushima',
'Naruto',
'Overcooked',
'Horizon',
'Tomb Raider',
'Uncharted',
'Person 5 Royal',
'Ratchet',
'Spider-Man',
];
var randomIndex = Math.floor(Math.random() * randomGameTitle.length);
return message.info(
'The game you will play is: ' + randomGameTitle[randomIndex] + '.',
);
};
return (
<div id="video-game" className="block bgGray">
<div className="container-fluid">
<div className="titleHolder">
<h2>Video Games</h2>
<p>A list of current games</p>
<div className="site-button-ghost-wrapper">
<Button
className="gameButton"
type="primary"
danger
ghost
onClick={chooseGame}
>
Pick for me
</Button>
</div>
</div>
<Row gutter={[16, 24]}>
{Games.map((videogame, i) => (
<Col span={8}>
<GameEntry
id={i}
key={i}
title={videogame.title}
imgURL={videogame.imgURL}
description={videogame.console}
/>
</Col>
))}
</Row>
</div>
</div>
);
}
export default VideoGameSection;
This is the code for my game entry component:
import React, { useState } from 'react';
import { Card, Button, Modal } from 'antd';
import YoutubeSection from './Home/YoutubeSection';
const { Meta } = Card;
function GameEntry(props) {
const [isModalVisible, setIsModalVisible] = useState(false);
const showModal = () => {
setIsModalVisible(true);
};
const handleClose = () => {
setIsModalVisible(false);
};
const handleCancel = () => {
setIsModalVisible(false);
};
return (
<div>
<Card
className="gameCard"
hoverable
cover={<img className="cardImg" alt={props.title} src={props.imgURL} />}
>
<div className="cardTitle">
<Meta title={props.title} description={props.description} />
</div>
<>
<Button
className="trailerButton"
type="primary"
block
style={{
color: '#fff',
borderColor: '#fff',
backgroundColor: '#e6544f',
}}
onClick={showModal}
>
Click for trailer
</Button>
<Modal
title={props.title}
width={'725px'}
visible={isModalVisible}
onOk={handleClose}
onCancel={handleCancel}
>
<YoutubeSection />
</Modal>
</>
</Card>
</div>
);
}
export default GameEntry;
This is the code for the youtube component:
import React, { useState } from 'react';
import ReactPlayer from 'react-player';
function YoutubeSection(props) {
return (
<div className="container-fluid">
<ReactPlayer
// url={videoTrailer}
muted={false}
playing={true}
controls={true}
/>
</div>
);
}
export default YoutubeSection;
example of data file:
const Games = [
{
id: 1,
title: 'Gears 5',
imgURL: '../Images/gears-5.jpeg',
console: 'Xbox',
videoID: 'SEpWlFfpEkU&t=7s',
},
You can keep a single Modal component and use it for that.
ModalView.js
import React, { useState } from "react";
import YoutubeSection from "./YoutubeSection";
import { Modal } from "antd";
const ModalView = ({
title,
isModalVisible,
handleClose,
handleCancel,
videoID
}) => {
return (
<Modal
title={title}
width={"725px"}
visible={isModalVisible}
onOk={handleClose}
onCancel={handleCancel}
>
<YoutubeSection videoID={videoID} />
</Modal>
);
};
export default ModalView;
Move the ModalView and its state, control functions to the VideoGameSection.
VideoGameSection.js
import React, { useState } from "react";
import { Button, message } from "antd";
import { Row, Col } from "antd";
import GameEntry from "./GameEntry";
import Games from "./Games";
import ModalView from "./ModalView";
function VideoGameSection() {
const [isModalVisible, setIsModalVisible] = useState(false);
const [currentVideoID, setCurrentVideoID] = useState("");
const showModal = () => {
setIsModalVisible(true);
};
const handleClose = () => {
setIsModalVisible(false);
};
const handleCancel = () => {
setIsModalVisible(false);
};
const chooseGame = () => {
var randomGameTitle = [
"Gears 5",
"Halo",
"Hellblade",
"It takes two",
"A Plague Tale",
"Psychonauts",
"Twelve Minutes",
"Ori",
"Streets of Rage",
"Last of Us",
"Boodborne",
"Geenshin Impact",
"Dragon Ball Z:KAKAROT",
"Ghost Tsushima",
"Naruto",
"Overcooked",
"Horizon",
"Tomb Raider",
"Uncharted",
"Person 5 Royal",
"Ratchet",
"Spider-Man"
];
var randomIndex = Math.floor(Math.random() * randomGameTitle.length);
return message.info(
"The game you will play is: " + randomGameTitle[randomIndex] + "."
);
};
return (
<div id="video-game" className="block bgGray">
<div className="container-fluid">
<div className="titleHolder">
<h2>Video Games</h2>
<p>A list of current games</p>
<div className="site-button-ghost-wrapper">
<Button
className="gameButton"
type="primary"
danger
ghost
onClick={chooseGame}
>
Pick for me
</Button>
</div>
</div>
<Row gutter={[16, 24]}>
{Games.map((videogame, i) => (
<Col span={8}>
<GameEntry
id={i}
key={i}
title={videogame.title}
imgURL={videogame.imgURL}
description={videogame.console}
videoID={videogame.videoID}
setCurrentVideoID={setCurrentVideoID}
showModal={showModal}
/>
</Col>
))}
<ModalView
videoID={currentVideoID}
handleClose={handleClose}
isModalVisible={isModalVisible}
/>
</Row>
</div>
</div>
);
}
export default VideoGameSection;
Access the videoID passed via ModalView. You can save gameId instead of videoID to get any other info of the game Ex:title.
YoutubeSection.js
import React, { useState } from "react";
import ReactPlayer from "react-player";
function YoutubeSection(props) {
return (
<div className="container-fluid">
<ReactPlayer
url={`https://www.youtube.com/watch?v=${props.videoID}`}
muted={false}
playing={true}
controls={true}
/>
</div>
);
}
export default YoutubeSection;
GameEntry.js
import React, { useState } from "react";
import { Card, Button, Modal } from "antd";
import YoutubeSection from "./YoutubeSection";
const { Meta } = Card;
function GameEntry(props) {
return (
<div>
<Card
className="gameCard"
hoverable
cover={<img className="cardImg" alt={props.title} src={props.imgURL} />}
>
<div className="cardTitle">
<Meta title={props.title} description={props.description} />
</div>
<>
<Button
className="trailerButton"
type="primary"
block
style={{
color: "#fff",
borderColor: "#fff",
backgroundColor: "#e6544f"
}}
onClick={() => {
props.setCurrentVideoID(props.videoID);
props.showModal();
}}
>
Click for trailer
</Button>
</>
</Card>
</div>
);
}
export default GameEntry;
Code sandbox => https://codesandbox.io/s/flamboyant-dan-z0kc0?file=/src/ModalView.js
I'm unable to input a value on the input field when selecting edit, i think it could be a onChange issue.
I look at something similar here, but the code seems to be outdated, and im using controlled components and not refs.
Editable.js this component renders an input field when edit is clicked
import React from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import TextField from '#material-ui/core/TextField';
const Editable = (props) => (
<div>
<TextField
id="outlined-name"
label="Title"
style={{width: 560}}
name="title"
value={props.editField}
onChange={props.onChange}
margin="normal"
variant="outlined"/>
</div>
)
export default Editable;
PostList.js renders a list of the post items
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost} from '../actions/';
import Editable from './Editable';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
// maybe their is issue with it calling title from name in the editable
// component
this.setState({
[e.target.title]: e.target.value
})
}
render(){
const {posts, editForm, isEditing} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={i} style={Styles.myPaper}>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={post.title} onChange={this.onChange}/>
): (
<div>
{post.title}
</div>
)}
</Typography>
<Typography component="p">
{post.post_content}
<h5>
by: {post.username}</h5>
<Typography color="textSecondary">{moment(post.createdAt).calendar()}</Typography>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm}>
Edit
</Button>
):(
<Button variant="outlined" type="submit" onClick={editForm}>
Update
</Button>
)}
<Button
variant="outlined"
color="primary"
type="submit"
onClick={this.removePost(post.id)}>
Remove
</Button>
</Paper>
))}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(null, mapDispatchToProps)(PostList);
Posts.js
import React, { Component } from 'react';
import PostList from './PostList';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
isEditing: false,
}
async componentWillMount(){
await this.props.GetPosts();
this.setState({ loading: false })
const reduxPosts = this.props.myPosts;
const ourPosts = reduxPosts
console.log(reduxPosts); // shows posts line 35
}
formEditing = () => {
if(this.state.isEditing){
this.setState({
isEditing: false
});
}
else{
this.setState({
isEditing:true
})
}
}
render() {
const {loading} = this.state;
const { myPosts} = this.props
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList isEditing={this.state.isEditing} editForm={this.formEditing} posts={myPosts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));
You're setting the value of the input with the property this.props.posts[index].title but you're handling the change through the PostLists state.
You should either delegate the onChange function to the component that's passing the list to your PostList component or store and update the list through the PostLists state.
you need to pass the value being set in change function
onChange = (e) => {
e.preventDefault();
// maybe their is issue with it calling title from name in the editable
// component
this.setState({
[e.target.title]: e.target.value
})
}
youre setting the state of your edit field. You have to reference that value again when you reference your Editable.
<Editable editField={this.state.[here should be whatever e.target.title was for editable change event] } onChange={this.onChange}/>
in your editable component your setting the value to the prop editField.
<TextField
id="outlined-name"
label="Title"
style={{width: 560}}
name="title"
**value={props.editField}**
onChange={props.onChange}
margin="normal"
variant="outlined"/>
hope that helps
By pressing a button and toggle showModal to true I am sending props to modal component AddEventModal. Inside AddEventModal component's constructor I assign a new state to open. After that I should be all set. But for some reason my open state in AddEventModal stays false.
Here is code:
import React, { Component } from 'react';
import Header from '../components/Header';
import Hour from '../components/Hour';
import Tile from '../components/Tile';
import CalEvent from '../components/CalEvent';
import AddButton from '../components/AddButton';
import AddEventModal from './AddEventModal';
import '../styles/calendar-grid.css';
class CalendarGrid extends Component {
constructor(){
super();
this.state = {
showModal: false
}
this.addEvent = this.addEvent.bind(this);
this.handleOpenModal = this.handleOpenModal.bind(this);
}
handleOpenModal() {
this.setState({ showModal: true });
}
handleCloseModal() {
this.setState({ showModal: false });
}
render(){
console.log(this.state.showModal);
const gridTiles = [];
return(
<div className="calendar-grid">
<AddButton showModal={this.handleOpenModal} />
<AddEventModal addEvent={this.addEvent} showModal={this.state.showModal}/>
</div>
)
}
}
export default CalendarGrid;
I am getting all props inside AddEventModal but it won't me to change open state.
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Typography from '#material-ui/core/Typography';
import Modal from '#material-ui/core/Modal';
import Button from '#material-ui/core/Button';
import Icon from '#material-ui/core/Icon';
import Close from '#material-ui/icons/Close';
import TextField from '#material-ui/core/TextField';
import DateRange from '#material-ui/icons/DateRange';
import Select from '#material-ui/core/Select';
import FormControl from '#material-ui/core/FormControl';
import MenuItem from '#material-ui/core/MenuItem';
import InputLabel from '#material-ui/core/InputLabel';
const styles = theme => ({
});
class AddEventModal extends React.Component {
constructor(props){
super(props);
this.state = {
open: this.props.showModal,
name: '',
date: new Date(),
time: ''
};
}
handleClose = () => {
this.setState({ open: false });
};
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
}
render() {
const { classes, showModal } = this.props;
console.log(`AddEventModal: ${this.props.showModal}`);
console.log(`AddEventModal: ${this.state.open}`);
return (
<div>
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.state.open}
onClose={this.handleClose}
>
<div style={getModalStyle()} className={classes.paper}>
<Close className={classes.closeIcon} onClick={this.handleClose} />
<Typography variant="h6" id="modal-title">
Create a New Event
</Typography>
<Typography variant="subtitle1" id="simple-modal-description">
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="standard-name"
label="Event"
className={classes.textField}
value={this.state.name}
onChange={this.handleChange('name')}
margin="normal"
/>
<div className={classes.dateAndTime}>
<TextField
id="date"
label="Date"
type="date"
defaultValue={this.state.date}
className={classes.textFieldDate}
InputLabelProps={{
shrink: true,
}}
/>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="time-simple">Time</InputLabel>
<Select
value={this.state.time}
onChange={this.handleChange('time')}
inputProps={{
name: 'time',
id: 'time-simple',
}}
>
{this.timeRange()}
</Select>
</FormControl>
</div>
<Button variant="outlined" className={classes.saveButton} onClick={this.handleClose}>Save</Button>
</form>
</Typography>
</div>
</Modal>
</div>
);
}
}
AddEventModal.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(AddEventModal);
Any advise will be appreciated.
You're setting the open state but it's being overwritten by the showModal state coming in from props.
You should pass a handler, e.g. toggleModal into the modal, and use that to change the open prop, not the state inside the modal.
Here's an example.
import React from "react";
import ReactDOM from "react-dom";
const Modal = ({ open, toggle }) => {
if (open) {
return (
<React.Fragment>
<h1>Modal</h1>
<button onClick={toggle}>Close</button>
</React.Fragment>
);
}
return null;
};
class App extends React.Component {
state = {
open: false
};
toggle = () => {
this.setState({ open: !this.state.open });
};
render() {
return (
<React.Fragment>
<button onClick={this.toggle}>Open</button>
<Modal open={this.state.open} toggle={this.toggle} />
</React.Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox here.
I'm trying to implement Sub-menu (nested menu).
It's worth to mention that I'm using hydra component and don't have previous experience with redux (started learning it a few days ago because of this specific problem).
I've followed the example provided on material-ui for nested list https://material-ui.com/demos/lists/#nested-list. And tutorial from
https://marmelab.com/react-admin/Theming.html#using-a-custom-menu for custom menu implementation.
So I have a few questions.
1) Can I have stateful component (MyMenu) just for handling toggling of menu items?
An example is not related to react-admin but its just example what I mean.
import React, { Component } from "react";
import { connect } from "react-redux";
import { addArticle } from "../actions/index";
const mapDispatchToProps = dispatch => {
return {
addArticle: article => dispatch(addArticle(article))
};
};
class ConnectedForm extends Component {
constructor() {
super();
this.state = {
title: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.id]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
const { title } = this.state;
const id = uuidv1();
this.props.addArticle({ title, id });
this.setState({ title: "" });
}
render() {
const { title } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
value={title}
onChange={this.handleChange}
/>
</div>
<button type="submit" className="btn btn-success btn-lg">
SAVE
</button>
</form>
);
}
}
const Form = connect(null, mapDispatchToProps)(ConnectedForm);
export default Form;
2) If not, can I achieve that by declaring a new state in store, for example, open: false, and then using the custom reducer to handle that.
3(bonus). If it's not a problem I would appreciate if someone can put me in the right direction which things to start learning first so I can less painfully manage to solve issues related to this amazing framework :)
The react-admin demo now shows a way to do so in examples/demo/src/layout/Menu.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import SettingsIcon from '#material-ui/icons/Settings';
import LabelIcon from '#material-ui/icons/Label';
import { withRouter } from 'react-router-dom';
import {
translate,
DashboardMenuItem,
MenuItemLink,
Responsive,
} from 'react-admin';
import visitors from '../visitors';
import orders from '../orders';
import invoices from '../invoices';
import products from '../products';
import categories from '../categories';
import reviews from '../reviews';
import SubMenu from './SubMenu';
class Menu extends Component {
state = {
menuCatalog: false,
menuSales: false,
menuCustomers: false,
};
static propTypes = {
onMenuClick: PropTypes.func,
logout: PropTypes.object,
};
handleToggle = menu => {
this.setState(state => ({ [menu]: !state[menu] }));
};
render() {
const { onMenuClick, open, logout, translate } = this.props;
return (
<div>
{' '}
<DashboardMenuItem onClick={onMenuClick} />
<SubMenu
handleToggle={() => this.handleToggle('menuSales')}
isOpen={this.state.menuSales}
sidebarIsOpen={open}
name="pos.menu.sales"
icon={<orders.icon />}
>
<MenuItemLink
to={`/commands`}
primaryText={translate(`resources.commands.name`, {
smart_count: 2,
})}
leftIcon={<orders.icon />}
onClick={onMenuClick}
/>
<MenuItemLink
to={`/invoices`}
primaryText={translate(`resources.invoices.name`, {
smart_count: 2,
})}
leftIcon={<invoices.icon />}
onClick={onMenuClick}
/>
</SubMenu>
<SubMenu
handleToggle={() => this.handleToggle('menuCatalog')}
isOpen={this.state.menuCatalog}
sidebarIsOpen={open}
name="pos.menu.catalog"
icon={<products.icon />}
>
<MenuItemLink
to={`/products`}
primaryText={translate(`resources.products.name`, {
smart_count: 2,
})}
leftIcon={<products.icon />}
onClick={onMenuClick}
/>
<MenuItemLink
to={`/categories`}
primaryText={translate(`resources.categories.name`, {
smart_count: 2,
})}
leftIcon={<categories.icon />}
onClick={onMenuClick}
/>
</SubMenu>
<SubMenu
handleToggle={() => this.handleToggle('menuCustomer')}
isOpen={this.state.menuCustomer}
sidebarIsOpen={open}
name="pos.menu.customers"
icon={<visitors.icon />}
>
<MenuItemLink
to={`/customers`}
primaryText={translate(`resources.customers.name`, {
smart_count: 2,
})}
leftIcon={<visitors.icon />}
onClick={onMenuClick}
/>
<MenuItemLink
to={`/segments`}
primaryText={translate(`resources.segments.name`, {
smart_count: 2,
})}
leftIcon={<LabelIcon />}
onClick={onMenuClick}
/>
</SubMenu>
<MenuItemLink
to={`/reviews`}
primaryText={translate(`resources.reviews.name`, {
smart_count: 2,
})}
leftIcon={<reviews.icon />}
onClick={onMenuClick}
/>
<Responsive
xsmall={
<MenuItemLink
to="/configuration"
primaryText={translate('pos.configuration')}
leftIcon={<SettingsIcon />}
onClick={onMenuClick}
/>
}
medium={null}
/>
<Responsive
small={logout}
medium={null} // Pass null to render nothing on larger devices
/>
</div>
);
}
}
const mapStateToProps = state => ({
open: state.admin.ui.sidebarOpen,
theme: state.theme,
locale: state.i18n.locale,
});
const enhance = compose(
withRouter,
connect(
mapStateToProps,
{}
),
translate
);
export default enhance(Menu);
I've searched the same question. But couldn't find a nested menu implementation. So I wrote my own. Check the code below;
import React, { Component, createElement } from "react";
import {
Admin,
Resource,
Layout,
MenuItemLink,
getResources
} from "react-admin";
import jsonServerProvider from "ra-data-json-server";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import {
List,
ListItem,
Collapse,
ListItemText,
ListItemIcon
} from "#material-ui/core";
import { ExpandLess, ExpandMore, StarBorder, LabelIcon } from "#material-ui/icons";
import { withStyles } from "#material-ui/core/styles";
const menuStyles = theme => ({
nested: {
paddingLeft: theme.spacing.unit * 4
}
});
class Menu extends Component {
menuList = [
{ name: "A", label: "Top menu 1", icon: <LabelIcon /> },
{ name: "B", label: "Top menu 2", icon: <LabelIcon /> },
{ name: "c", label: "Top menu 3", icon: <LabelIcon /> }
];
constructor(props) {
super(props);
this.state = { open: "A" };
}
render() {
const { resources, onMenuClick, logout } = this.props;
return (
<div>
<List component="nav">
{this.menuList.map(menu => {
return (
<div key={menu.name}>
<ListItem
button
onClick={() => this.setState(state => ({ open: menu.name }))}
>
<ListItemIcon>{menu.icon}</ListItemIcon>
<ListItemText inset primary={menu.label} />
{this.state.open == menu.name ? (
<ExpandLess />
) : (
<ExpandMore />
)}
</ListItem>
<Collapse
in={this.state.open == menu.name}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
{resources
.filter(x => x.options.menu == menu.name)
.map((resource, i) => (
<MenuItemLink
key={"m" + i}
to={`/${resource.name}`}
primaryText={resource.options.label || resource.name}
leftIcon={
resource.icon
? createElement(resource.icon)
: undefined
}
onClick={onMenuClick}
className={this.props.classes.nested}
/>
))}
</List>
</Collapse>
</div>
);
})}
</List>
</div>
);
}
}
var MenuWithStyles = withStyles(menuStyles)(Menu);
const MyMenu = withRouter(
connect(state => ({
resources: getResources(state)
}))(MenuWithStyles)
);
const MyLayout = props => <Layout {...props} menu={MyMenu} />;
const App = () => (
<Admin
...
appLayout={MyLayout}
>
<Resource
...
options={{ label: 'Page 1' menu: "A" }}
/>
<Resource
...
options={{ label: 'Page 2' menu: "A" }}
/>
<Resource
...
options={{ label: 'Page 3' menu: "B" }}
/>
</Admin>
);