I am using ChakraUI and react and I want to open my NavigationDrawer using a button on my NavigationBar. I need to call the useDisclosure from my drawer component in my bar compoment. How would I do that? I couldn't find online how to do this.
My NavigationDrawer: component
import {
Drawer,
DrawerOverlay,
DrawerContent,
DrawerCloseButton,
IconButton,
useDisclosure,
} from '#chakra-ui/react'
import { HamburgerIcon } from '#chakra-ui/icons'
import { React } from 'react'
function NavigationDrawer() {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<>
<IconButton icon={<HamburgerIcon />} aria-label='Open navigation drawer' variant='solid' onClick={onOpen}>
Open
</IconButton>
<Drawer placement='left' onClose={onClose} isOpen={isOpen}>
<DrawerContent>
<DrawerCloseButton aria-label='Open navigation drawer'></DrawerCloseButton>
</DrawerContent>
</Drawer>
</>
)
}
export default NavigationDrawer;
My NavigationBar component:
// src/components/navigation/NavigationBar.jsx
import { IconButton, useColorMode, Flex } from '#chakra-ui/react'
import { MoonIcon, SunIcon, HamburgerIcon } from '#chakra-ui/icons'
function NavigationBar() {
const { colorMode, toggleColorMode } = useColorMode()
return (
<>
<Flex
as="nav"
align="center"
justify="space-between"
wrap="wrap"
w="100%"
mb={8}
p={8}
bg={"black"}
>
<IconButton icon={<HamburgerIcon />} aria-label='Open navigation drawer' variant='solid'>
Open
</IconButton>
<IconButton aria-label='Toggle theme' onClick={toggleColorMode} icon={colorMode === 'light' ? <SunIcon /> : <MoonIcon />} />
</Flex>
</>
)
}
export default NavigationBar
How I combine my NavigationDrawer and NavigationBar:
// src/components/navigation/Navigation.jsx
import NavigationDrawer from './NavigationDrawer';
import NavigationBar from './NavigationBar';
function Navigation() {
return (
<>
<NavigationDrawer />
<NavigationBar />
</>
)
}
export default Navigation
One option would be to move the useDisclosure hook up one level and pass the props down to the drawer and navigation bar.
//Navigation.jsx
import { useDisclosure } from '#chakra-ui/react'
import NavigationDrawer from './NavigationDrawer'
import NavigationBar from './NavigationBar'
function Navigation() {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<>
<NavigationDrawer isOpen={isOpen} onClose={onClose} />
<NavigationBar onClick={onOpen} />
</>
)
}
export default Navigation
//NavigationDrawer.jsx
import { Drawer, DrawerOverlay, DrawerContent, DrawerCloseButton } from '#chakra-ui/react'
import { React } from 'react'
function NavigationDrawer({ isOpen, onClose }) {
return (
<Drawer placement="left" onClose={onClose} isOpen={isOpen}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton aria-label="Open navigation drawer"></DrawerCloseButton>
</DrawerContent>
</Drawer>
)
}
export default NavigationDrawer
//NavigationBar.jsx
import { IconButton, useColorMode, Flex } from '#chakra-ui/react'
import { MoonIcon, SunIcon, HamburgerIcon } from '#chakra-ui/icons'
function NavigationBar({ onClick }) {
const { colorMode, toggleColorMode } = useColorMode()
return (
<Flex as="nav" align="center" justify="space-between" wrap="wrap" w="100%" mb={8} p={8} bg={'black'}>
<IconButton
icon={<HamburgerIcon />}
aria-label="Open navigation drawer"
variant="solid"
onClick={onClick}
>
Open
</IconButton>
<IconButton
aria-label="Toggle theme"
onClick={toggleColorMode}
icon={colorMode === 'light' ? <SunIcon /> : <MoonIcon />}
/>
</Flex>
)
}
export default NavigationBar
Related
I am implanting the dark & light mode using Tailwind inside my Next 13 app. I am able to show the correct current theme dark or light when showing text only. But when I try to use icons the icon has a delay, or doesn't show the correct icon on toggle & reload even with logic check.
_app.tsx:
import "#styles/globals.css";
import { NEXT_SEO_DEFAULT } from "#root/next-seo.config";
import { AnimatePresence } from "framer-motion";
import { ThemeProvider } from "next-themes";
import { NextSeo } from "next-seo";
import type { AppProps } from "next/app";
function MyApp({ Component, pageProps, router }: AppProps) {
return (
<>
<NextSeo {...NEXT_SEO_DEFAULT} />
<ThemeProvider
attribute="class"
enableSystem={false}
defaultTheme="light"
>
<AnimatePresence
mode="wait"
initial={false}
onExitComplete={() => window.scrollTo(0, 0)}
>
<Component {...pageProps} key={router.pathname} />
</AnimatePresence>
</ThemeProvider>
</>
);
}
export default MyApp;
ThemeSwitch.tsx:
"use client";
import { useTheme } from "next-themes";
import { SunIcon, MoonIcon } from "#heroicons/react/24/outline";
const ThemeSwitch = () => {
const { theme, setTheme, resolvedTheme } = useTheme();
const CurrentTheme = () => {
return theme === "light" ? (
<MoonIcon className="w-6 h-8 text-hotpink" />
) : (
<SunIcon className="w-6 h-8 text-hotpink" />
);
};
return (
<div className="items-center hidden space-x-5 lg:flex">
<button
onClick={() => {
setTheme(theme === "light" ? "dark" : "light");
}}
>
<CurrentTheme />
</button>
</div>
);
};
export default ThemeSwitch;
I would like to make it possible that whenever I click on a button (which is a component itself) a modal component is called. I did some research but none of the solutions is doing what I need. It partially works but not quite the way I want it to because, once the state is set to true and I modify the quantity in the Meals.js and click again the modal doesn't show up
Meals.js
import React, { useState } from "react";
import { Card } from "react-bootstrap";
import { Link } from "react-router-dom";
import NumericInput from "react-numeric-input";
import AddBtn from "./AddtoCartBTN";
function Meals({ product }) {
const [Food, setFood] = useState(0);
return (
<Card className="my-3 p-3 rounded">
<Link to={`/product/${product.id}`}>
<Card.Img src={product.image} />
</Link>
<Card.Body>
<Link to={`/product/${product.id}`} style={{ textDecoration: "none" }}>
<Card.Title as="div">
<strong>{product.name}</strong>
</Card.Title>
</Link>
Quantity{" "}
<NumericInput min={0} max={100} onChange={(value) => setFood(value)} />
{/* <NumericInput min={0} max={100} onChange={ChangeHandler(value)} /> */}
<Card.Text as="h6" style={{ color: "red" }}>
{Food === 0 ? product.price : Food * product.price} CFA
</Card.Text>
<AddBtn quantity={Food} price={product.price} productId={product.id} />
</Card.Body>
</Card>
);
}
export default Meals;
AddToCartBtn.js
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { addToCart } from "../actions/cmdActions";
import toast, { Toaster } from "react-hot-toast";
import CartModal from "./CartModal";
function AddtoCartBTN({ quantity, price, productId }) {
const AddedPrice = quantity * price;
const [showModal, setShowModal] = useState(false)
const notify = () => toast("Ajout Effectue !", {});
const dispatch = useDispatch();
const AddtoCart = () => {
// console.log("Added to cart !");
// console.log("Added : ", AddedPrice);
if (AddedPrice > 0) {
dispatch(addToCart(productId, AddedPrice));
notify();
setShowModal(true)
}
};
return (
<button
className="btn btn-primary d-flex justify-content-center"
onClick={AddtoCart}
>
Ajouter
{showModal && <CartModal/>}
<Toaster />
</button>
);
}
export default AddtoCartBTN;
Modal.js
import React, { useState } from "react";
import { Modal, Button, Row, Col } from "react-bootstrap";
import { useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
function CartModal () {
const [modalShow, setModalShow] = useState(true);
let history = useHistory();
const panierHandler = () => {
history.push('/commander')
}
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
function MyVerticallyCenteredModal(props) {
return (
<Modal
{...props}
size="md"
aria-labelledby="contained-modal-title-vcenter"
centered
backdrop="static"
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
<b style={{ color: "red" }}> choix de produits</b> <br />
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Row>
<Col><b>Nom</b></Col>
<Col><b>Quantite</b></Col>
</Row><hr/>
{cartItems && cartItems.map((item,i)=>(
<>
<Row key={i}>
<Col>{item.name}</Col>
<Col>{item.total / item.price}</Col>
</Row><hr/>
</>
))}
</Modal.Body>
<Modal.Footer
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Button
// onClick={props.onHide}
onClick={panierHandler}
>
Aller au Panier
</Button>
</Modal.Footer>
</Modal>
);
}
return (
<>
<MyVerticallyCenteredModal
show={modalShow}
onHide={() => setModalShow(false)}
/>
</>
);
}
export default CartModal
in AddToCartBtn.js, you can pass setShowModal as a prop.
{showModal && <CartModal setShowModal={setShowModal} />}
And the, in Modal.js, you can use the prop setShowModal to set the value to false
<MyVerticallyCenteredModal show={true} onHide={() => setShowModal(false)} />
Eliminate the state in Modal.js, keep visible as always true
I am working on an app with React and Redux and displaying some data from API in TextInput control. But now I am not able to edit the data in the TextInput. Following is my complete code of the class:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Article from "grommet/components/Article";
import Box from "grommet/components/Box";
import Button from "grommet/components/Button";
import Header from "grommet/components/Header";
import Heading from "grommet/components/Heading";
import Section from "grommet/components/Section";
import AdminMenu from "../../components/Nav/Admin";
import NavControl from "../../components/Nav/Control";
import { getMessage } from "grommet/utils/Intl";
import Notices from "../../components/Notices";
import CheckBox from "grommet/components/CheckBox";
import TextInput from "grommet/components/TextInput";
import { pageLoaded } from "../utils";
import {
recognitionSettingsLoaded,
recognitionSettingsSaved,
} from "../../actions/settings-recognition";
import dashboard from "../../reducers/dashboard";
class Settings extends Component {
constructor(props) {
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount() {
const { dispatch, settingRecognition } = this.props;
console.log(this.props.state);
console.log(dashboard);
dispatch(recognitionSettingsLoaded("2"));
pageLoaded("Configuration");
}
onSave() {
const { survey, dispatch } = this.props;
dispatch(
recognitionSettingsSaved(
this.props.settingRecognition.days,
this.props.settingRecognition.active
)
);
}
handleDaysChange(e) {
const days = e.target.value;
settingRecognition.days = days;
}
handleActiveChange(e) {
const active = e.target.value;
settingRecognition.active = active;
}
render() {
const { dispatch, settingRecognition } = this.props;
console.log("render method");
console.log(settingRecognition);
const { intl } = this.context;
return (
<Article primary={true}>
<Header
direction="row"
justify="between"
size="large"
pad={{ horizontal: "medium", between: "small" }}
>
<NavControl name={getMessage(intl, "Configuration")} />
<AdminMenu />
</Header>
<Box pad={{ horizontal: "medium", vertical: "medium" }}>
<Heading tag="h4" margin="none">
{getMessage(intl, "RecognitionLifetime")}
</Heading>
<Heading tag="h5" margin="none">
{getMessage(intl, "DefineIsRecognitionTemporary")}
</Heading>
<Box direction="row">
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "NewUserActive")}
</Heading>
</Box>
<Heading tag="h3" margin="none">
{getMessage(intl, "HideAfter")}
</Heading>
<Box direction="row">
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "Days")}
</Heading>
</Box>
<Button
path="/recognition-settings"
label={getMessage(intl, "NewUserSave")}
primary={true}
onClick={() => {
this.onSave();
}}
/>
</Box>
<Notices />
</Article>
);
}
}
Settings.propTypes = {
dispatch: PropTypes.func.isRequired,
settingRecognition: PropTypes.object.isRequired,
};
Settings.contextTypes = {
intl: PropTypes.object,
};
const mapStateToProps = (state) => ({
settingRecognition: state.settingRecognition,
});
export default connect(mapStateToProps)(Settings);
I have created handleDaysChange function which should run on the text change of TextInput control. I have done similar thing for the checkbox and that works fine but I am not able to get it working for the TextInput.
You are not binding your change events.
Try this....
class Settings extends Component {
constructor(props){
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount(){
....
}
......
}
and change this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={(e) => this.handleActiveChange(e)}
/>
To this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>
same for text input
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>
You need to set up two-way-binding so that the content of the textInput reflects the prop that you set in your onChange function. Try giving your textInput a property of value={this.settingRecognition.days}
I have Header.js component. Inside it, I have two AppBar, first AppBar is sticky, and the second is not. By default, only second AppBar showed. When we scroll, I want the second AppBar to collapse and the first AppBar to show stickied in the top of the screen.
I have seen useScrollTrigger() from Material-ui documentation here, but it only show to hide AppBar on scroll.
// Header.js
import React from "react";
import { AppBar, Toolbar, Typography } from "#material-ui/core";
export default function Header() {
return (
<>
<AppBar position="static">
<Toolbar>
<Typography variant="h6">First AppBar</Typography>
</Toolbar>
</AppBar>
<AppBar position="static">
<Toolbar>
<Typography variant="h6">Second AppBar</Typography>
</Toolbar>
</AppBar>
</>
);
}
Here is my sandbox link
This code seems run like you want. I used material-ui demo
import React from "react";
import { AppBar, Toolbar, Typography } from "#material-ui/core";
import useScrollTrigger from '#material-ui/core/useScrollTrigger';
import Slide from '#material-ui/core/Slide';
function HideOnScroll(props) {
const { children, window } = props;
const trigger = useScrollTrigger({ target: window ? window() : undefined });
return (
<Slide appear={false} direction="down" in={!trigger}>
{children}
</Slide>
);
}
function ElevationScroll(props) {
const { children, window } = props;
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
target: window ? window() : undefined,
});
return React.cloneElement(children, {
elevation: trigger ? 4 : 0,
});
}
export default function Header(props) {
return (
<>
<ElevationScroll {...props}>
<AppBar>
<Toolbar>
<Typography variant="h6">First AppBar</Typography>
</Toolbar>
</AppBar>
</ElevationScroll >
{/* second appbar */}
<HideOnScroll {...props}>
<AppBar>
<Toolbar>
<Typography variant="h6">Second AppBar</Typography>
</Toolbar>
</AppBar>
</HideOnScroll>
</>
);
}
I have a sample code for App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Child from './child';
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<Child ref={instance => { this.child = instance; }}/>
<button onClick={() => { this.child.onAlert(); }}>Click</button>
</div>
);
}
}
export default App;
And child component like
import React, { Component } from 'react';
class Child extends Component {
state = { }
onAlert =()=>
alert("hey");
render() {
return (
<div> IM kid</div>
);
}
}
export default Child;
here when I click on button in App.js I am able to get the expected output
i.e., i am able to call the child function onAlert()
I am using the same scenario in material-ui and react where I need to trigger Drawer Component from Toolbar Component
and my code is like Titlebar.js below code is my parent component here
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Toolbar from 'material-ui/Toolbar'
import AppBar from 'material-ui/AppBar';
import Typography from 'material-ui/Typography';
import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu';
import { withStyles, createStyleSheet } from 'material-ui/styles';
import Child from './child';
const styleSheet = createStyleSheet('Titlebar', () => ({
root: {
position: 'relative',
width: '100%',
},
appBar: {
position: 'relative',
},
flex: {
flex: 1,
}
}));
class TitleBar extends Component {
render() {
const classes = this.props.classes;
return (
<div className={classes.root}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton contrast onClick={() => { this.child.handleLeftOpen(); }}>
<MenuIcon />
</IconButton>
<Typography type="title" colorInherit className={classes.flex}>Productivity Dashboard</Typography>
</Toolbar>
</AppBar>
<Child ref={instance => { this.child = instance; }}/>
</div>
)
}
}
TitleBar.PropTypes={
classes:PropTypes.object.isRequired,
}
export default withStyles(styleSheet)(TitleBar);
and my child component code Child.js is below
import React, { Component } from 'react';
import { withStyles, createStyleSheet } from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import Divider from 'material-ui/Divider';
import InboxIcon from 'material-ui-icons/Inbox';
import DraftsIcon from 'material-ui-icons/Drafts';
import StarIcon from 'material-ui-icons/Star';
import SendIcon from 'material-ui-icons/Send';
import MailIcon from 'material-ui-icons/Mail';
import DeleteIcon from 'material-ui-icons/Delete';
import ReportIcon from 'material-ui-icons/Report';
const styleSheet = createStyleSheet('Child', {
list: {
width: 250,
flex: 'initial',
},
listFull: {
width: 'auto',
flex: 'initial',
},
});
class Child extends Component {
state = {
open: {
top: false,
left: false,
bottom: false,
right: false,
},
}
handleLeftOpen = () =>{
console.log("im here")
this.toggleDrawer('left', true);
}
handleLeftClose = () => this.toggleDrawer('left', false);
toggleDrawer = (side, open) => {
const drawerState = {};
drawerState[side] = open;
this.setState({ open: drawerState });
};
render() {
const classes=this.props.classes;
return (
<Drawer
open={this.state.open.left}
onRequestClose={this.handleLeftClose}
onClick={this.handleLeftClose}
>
<List className={classes.list} disablePadding>
<ListItem button>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItem>
<ListItem button>
<ListItemIcon>
<StarIcon />
</ListItemIcon>
<ListItemText primary="Starred" />
</ListItem>
<ListItem button>
<ListItemIcon>
<SendIcon />
</ListItemIcon>
<ListItemText primary="Send mail" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItem>
</List>
<Divider />
<List className={classes.list} disablePadding>
<ListItem button>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText primary="All mail" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DeleteIcon />
</ListItemIcon>
<ListItemText primary="Trash" />
</ListItem>
<ListItem button>
<ListItemIcon>
<ReportIcon />
</ListItemIcon>
<ListItemText primary="Spam" />
</ListItem>
</List>
</Drawer>
);
}
}
export default withStyles(styleSheet)(Child);
Here I am calling handleLeftOpen() function from my parent when I click on the IconButton in the Tiltlbar Component I am not getting the expected output. I am getting error like below in my console
Uncaught TypeError: Cannot read property 'handleLeftOpen' of null
at onClick (http://localhost:3000/static/js/bundle.js:90993:50)
at Object.ReactErrorUtils.invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:17236:17)
at executeDispatch (http://localhost:3000/static/js/bundle.js:17019:22)
at Object.executeDispatchesInOrder (http://localhost:3000/static/js/bundle.js:17042:6)
at executeDispatchesAndRelease (http://localhost:3000/static/js/bundle.js:16430:23)
at executeDispatchesAndReleaseTopLevel (http://localhost:3000/static/js/bundle.js:16441:11)
at Array.forEach (native)
at forEachAccumulated (http://localhost:3000/static/js/bundle.js:17339:10)
at Object.processEventQueue (http://localhost:3000/static/js/bundle.js:16644:8)
at runEventQueueInBatch (http://localhost:3000/static/js/bundle.js:24266:19)
please check the code and let me know if anything need to be changed
The difference here is that in your first example you export:
export default Child;
In the second example you export:
export default withStyles(styleSheet)(Child);
This returns a decorated component, so the ref is put on this decorated component and not your Child component. To solve this issue the decorated component accepts a property called innerRef so you can pass a ref to your own component. So to solve this you change:
<Child ref={instance => { this.child = instance; }}/>
to
<Child innerRef={instance => { this.child = instance; }}/>