I am trying to make a simple navbar using material ui with a few buttons and a custom drop down menu component. When I try to style it using the makeStyle hook, the styling only applies to the material ui's buttons and heading but not the custom drop down component.
import React, { useContext } from "react";
import { makeStyles } from "#material-ui/core/styles";
import { AppBar, Toolbar, Typography } from "#material-ui/core";
import DropDown from "./DropDown";
import { Button } from "#material-ui/core";
import { AlgoContext } from "../AlgoContext";
const useStyles = makeStyles((theme) => ({
item: {
marginRight: theme.spacing(5),
},
}));
const MainHeader = () => {
const classes = useStyles();
const [algo, setAlgo] = useContext(AlgoContext);
return (
<div>
<AppBar elevation={0} position='static'>
<Toolbar>
<Typography variant='h6' className={classes.item}>
Pathfinding Visualiser
</Typography>
<Button variant='contained' className={classes.item}>
Visualise {algo.type}
</Button>
<DropDown className={classes.item}></DropDown>
<Button variant='contained' className={classes.item}>
Clear walls
</Button>
<Button variant='contained' className={classes.item}>
Clear path
</Button>
</Toolbar>
</AppBar>
</div>
);
};
export default MainHeader;
className is a default attribute of React element. You can not style your custom component by passing style object via className. Instead of that, you should pass it as a prop to DropDown component. Try this:
const MainHeader = () => {
const classes = useStyles();
const [algo, setAlgo] = useContext(AlgoContext);
return (
<DropDown itemStyle={classes.item}></DropDown>
);
};
export default MainHeader;
const DropDown = (props) => {
...
return (
<div className={props.itemStyle}>
...
</div>
)
}
Related
When I click on one button in a navigation bar, I want to only flip one icon instead of all of the icons in the navigation bar. Right now when I click on one button, all of the icons flip instead of just the one I clicked.
Here is a demo: https://codesandbox.io/s/festive-frost-53nny
Any help is greatly appreciated.
You are using one open state for all items. Create an array of open items like your anchor Array.
import "./styles.css";
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import clsx from "clsx";
import Button from "#material-ui/core/Button";
import { KeyboardArrowUp } from "#material-ui/icons";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import { menuItems } from "./menuItems";
const useStyles = makeStyles((theme) => ({
openX: {
transform: "scaleX(1)"
},
closeX: {
transform: "scaleX(-1)"
},
openY: {
transform: "scaleY(1)"
},
closeY: {
transform: "scaleY(-1)"
}
}));
export default function App() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (index, event) => {
setAnchorEl({ [index]: event.currentTarget });
setOpen((prevState) => {
const newState = [...prevState];
newState[index] = !prevState[index];
return newState;
});
};
const [open, setOpen] = React.useState(
new Array(Object.keys(menuItems).length).fill(false)
);
return (
<AppBar>
<Toolbar>
<div>
{Object.keys(menuItems).map((item, index) => (
<div key={index}>
<React.Fragment>
<Button onClick={(e) => handleClick(index, e)}>
{item}
<KeyboardArrowUp
className={clsx(
!open[index] && classes.closeY,
open[index] && classes.openY
)}
/>
</Button>
</React.Fragment>
</div>
))}
</div>
</Toolbar>
</AppBar>
);
}
I have been trying to create a component per function in my app, but I am facing the following issue.
I have the component DisplayAllData that sends the data and an actionable button to DisplayDataWithButton, the issue is that when someone clicks on the Button send in the props, the function modifies the state of the parent component, which is also sent as a parameter to FullScreenDialog, and that throws a Warning: Cannot update a component while rendering a different component.
I designed the components in this particular way because:
DisplayAllData is the only function that has the data to render and the actionable button. (Model)
DisplayDataWithButton only renders the data and displays the actionable components for that particular data, in this case a button that opens a Dialog in screen. (Viewer)
You can find a running example here: https://codesandbox.io/s/material-demo-forked-8oyef
import React from "react";
import Button from "#material-ui/core/Button";
import DisplayDataWithButton from "./DisplayDataWithButton";
import FullScreenDialog from "./fullscreendialog";
export default function App(props) {
const [openFullScreen, setopenFullScreen] = React.useState(false);
var items = ["John", "Melinda"];
var dataDisplayFunction = (data) => {
return data.map((item) => {
return [
item,
<Button
color="success"
size="small"
className="px-2"
variant="contained"
onClick={setopenFullScreen()}
>
Show Dialog
</Button>
];
});
};
return (
<>
<DisplayDataWithButton
shapeDataFunction={dataDisplayFunction}
data={items}
/>
<FullScreenDialog open={openFullScreen} />
</>
);
}
DisplayDataWithButton.js
export default function DisplayDataWithButton(props) {
return props.shapeDataFunction(props.data);
}
I suspect that there is another way to implement this model, any suggestion, or ideas on how to fix this one.
Thanks
A couple of things: "I have been trying to create a component per function in my app". Forget that - the pattern you have opted for here is called render props but I don't see how it is necessary. Keep it simple. If a big component is simpler to understand than a small component I always opt for the bigger component. Splitting your components will not magically make them easier to understand.
All of the warnings have been dealt with. Most of them were simple mistakes, for example: onClick={setopenFullScreen()} should be onClick={setopenFullScreen}. You can compare your sandbox with my sandbox for all of the changes.
import React from "react";
import ReactDOM from "react-dom";
import Button from "#material-ui/core/Button";
import FullScreenDialog from "./fullscreendialog";
export default function App() {
const [openFullScreen, setopenFullScreen] = React.useState(false);
const items = ["John", "Melinda"];
return (
<>
{items.map((item) => [
item,
<Button
key={item}
color="primary"
size="small"
className="px-2"
variant="contained"
onClick={() => setopenFullScreen((prev) => !prev)}
>
Show Dialog
</Button>
])}
<FullScreenDialog open={openFullScreen} />
</>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
import React from "react";
import { makeStyles } from "#material-ui/core";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import ListItemText from "#material-ui/core/ListItemText";
import ListItem from "#material-ui/core/ListItem";
import List from "#material-ui/core/List";
import Divider from "#material-ui/core/Divider";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import Typography from "#material-ui/core/Typography";
import CloseIcon from "#material-ui/icons/Close";
import Slide from "#material-ui/core/Slide";
const useStyles = makeStyles((theme) => ({
appBar: {
position: "relative"
},
title: {
marginLeft: theme.spacing(2),
flex: 1
}
}));
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
export default function FullScreenDialog(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
fullScreen
open={props.open}
onClose={handleClose}
TransitionComponent={Transition}
>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
Sound
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
<List>
<ListItem button>
<ListItemText primary="Phone ringtone" secondary="Titania" />
</ListItem>
<Divider />
<ListItem button>
<ListItemText
primary="Default notification ringtone"
secondary="Tethys"
/>
</ListItem>
</List>
</Dialog>
</div>
);
}
I have a component called Component1 in which I have the following code:
import React, { useState } from "react";
import Popover from "material-ui-popup-state/HoverPopover";
import Fab from "#material-ui/core/Fab";
import {
usePopupState,
bindHover,
bindPopover
} from "material-ui-popup-state/hooks";
import PaletteIcon from "#material-ui/icons/Palette";
import Colors from "./Colors";
const DEFAULT_COLOR = "red";
const COLORS = [/*list of colors*/];
const Component1 = ({ classes }) => {
const popupState = usePopupState({
variant: "popover",
popupId: "demoPopover"
});
const [selectedColor, setSelectedColor] = useState(DEFAULT_COLOR);
return (
<div className="box" style={{ backgroundColor: selectedColor }}>
<Fab variant="extended" {...bindHover(popupState)}>
<PaletteIcon />
</Fab>
<Popover
>
<div className="color-palette">
{COLORS.map((color) => (
<Colors
key={color}
selected={selectedColor === color}
onClick={setSelectedColor}
color={color}
/>
))}
</div>
</Popover>
</div>
);
};
export default Component1;
This component is imported in Component2 where the code is:
import React from "react";
import Component1 from "./Component1";
import Fab from "#material-ui/core/Fab";
import DeleteIcon from "#material-ui/icons/Delete";
function Component2(props) {
function handleClick() {
props.onDelete(props.id);
}
return (
<div className="note" style={{ backgroundColor: "selectedColor" }}>
<h1>{props.title}</h1>
<p>{props.content}</p>
<Fab onClick={handleClick}>
<DeleteIcon fontSize="small" />
</Fab>
<HoverPopover />
</div>
);
}
export default Component2;
In component2 I need to use the const selectedColor for styling purpose for div with class="note". However the issue is when I select colors from COLORS list the background-color of div with class="note" is not changing. I tried many options but I don't understand how to do it correctly. Please tell me how to do it right.
To share the "selectedColor" variable, which is actually a state, you would have to pass it through the props to the child component
Your "Component2" should declare the state "selectedColor", and this state and its function must be passed by the props to your "Component1".
https://reactjs.org/tutorial/tutorial.html#lifting-state-up
The Material-UI Box component allows us to reference other components as follows:
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
const NewButton = ({ children }) => (
<Box compoment={Button} px={3} py={1} color="white" bgcolor="primary.dark">
{children}
</Box>
)
This works just as I want it to. However, let me now try it with the Drawer component:
import Drawer from "#material-ui/core/Drawer";
import Box from "#material-ui/core/Box";
const NewDrawer = ({ children, open }) => {
return (
<Box component={Drawer} width="300px" bgcolor="secondary.dark">
{children}
</Box>
)
}
This does not work.
Any idea why not and how I can get it to work?
Thanks.
As per Material UI doc, For the Drawer component, we have to pass the open prop as true.
And also need to pass the drawer content like below,
<Drawer open={true}>{renderContents()}</Drawer>
In Box component api, we can pass the component data as a 'function'. like the below example.
import Drawer from "#material-ui/core/Drawer";
import Box from "#material-ui/core/Box";
const NewDrawer = ({ children, open }) => {
return (
<Box component={() => {
return <Drawer open={true}>{renderContents()}</Drawer>
}} width="300px" bgcolor="secondary.dark">
{children}
</Box>
)
}
Refer to my code sandbox example.
When using a Drawer with the temporary variant (the default), the className prop gets applied to the Modal which is not the element that you are trying to style.
Instead, you want to apply the styles from the Box to the Paper element within the Drawer. You can accomplish this using the render props approach for the Box children as shown in my example below.
import React from "react";
import Drawer from "#material-ui/core/Drawer";
import Box from "#material-ui/core/Box";
import Button from "#material-ui/core/Button";
const BoxDrawer = explicitProps => {
return (
<Box width="300px" bgcolor="secondary.dark">
{({ className }) => (
<Drawer {...explicitProps} PaperProps={{ className: className }} />
)}
</Box>
);
};
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<div className="App">
<Button variant="contained" onClick={() => setOpen(!open)}>
Open Drawer
</Button>
<BoxDrawer open={open} onClose={() => setOpen(false)}>
<div style={{ textAlign: "center" }}>
<h1>Hello CodeSandbox</h1>
<Button variant="contained" onClick={() => setOpen(!open)}>
Close Drawer
</Button>
</div>
</BoxDrawer>
</div>
);
}
I am trying to use a navbar from the Material UI collection however the component was written as a function and was using hooks. I'm trying to convert the component to a HOC (class) and I am having issues with accessing the theme in the component. Theme in the component in undefined
const styles = theme => ({
root: {
display: "flex"
},
});
<IconButton onClick={this.handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
Try this:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core';
import Paper from './Paper';
const styles = () => ({
root: {
display: 'flex'
}
});
const Bubble = props => {
const { classes } = props;
return (
<IconButton className={classes.root} onClick={this.handleDrawerClose}></IconButton>
);
};
export default withStyles(styles)(Bubble);