React - how to click outside to close the tooltip - javascript

This is my current tooltip.
I am using react-power-tooltip
When I click the button, I can close the tooltip.
But I want to close the tooltip when I click outside the tooltip.
How am I supposed to do it?
App.js
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
return (
<div className="App">
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
);
}
TooltipList
import React from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show } = props;
return (
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
);
}
export default Tooptip;
CodeSandbox:
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js
Update 1:
I update the code based on the answer.
It can now click outside to close, but if I click the button to close the tooltip, it's not working.
App.js
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
return (
<div className="App">
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList
show={showTooltip}
onClose={() => {
setShowTooltip();
}}
/>
</button>
</div>
);
}
TooltipList.js
import React, { useEffect, useRef } from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show, onClose } = props;
const containerRef = useRef();
useEffect(() => {
if (show) {
containerRef.current.focus();
}
}, [show]);
return (
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
onClose();
}}
>
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
</div>
);
}
export default Tooptip;
Codesandbox
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js:572-579

As I can see, you are using material-ui for icons, so there is an option known as ClickAwayListner within material-ui
App.js
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const handleClickAway = () => {
setShowTooltip(false);
}
return (
<div className="App">
<ClickAwayListener onClickAway={handleClickAway}>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={e => {
e.stopPropagation();
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</ClickAwayListener>
</div>
);
}
Wrap your container with ClickAwayListener

You should add a wrapper element to detect if the click is outside a component
then the showTooltip to false at your code:
codeSanbox
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState, useEffect, useRef } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const outsideClick = (ref) => {
useEffect(() => {
const handleOutsideClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setShowTooltip(false);
}
};
// add the event listener
document.addEventListener("mousedown", handleOutsideClick);
}, [ref]);
};
const wrapperRef = useRef(null);
outsideClick(wrapperRef);
return (
<div className="App" ref={wrapperRef}>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
);
}

Here is a crazy little idea. I wrapped your component in an inline-flex div and gave it focus on load. Then added an onBlur event which will hide the menu if you click anywhere else. This can be used if you don't want to give focus on any other element on the page.
https://codesandbox.io/s/epic-kapitsa-yh7si?file=/src/App.js:0-940
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const containerRef = useRef();
useEffect(() => {
containerRef.current.focus();
}, []);
return (
<div className="App">
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
debugger;
setShowTooltip(false);
}}
>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
</div>
);
}
Update 1:
The problem was your button click was called every time you select an item that toggles your state. I have updated the code to prevent that using a useRef that holds a value.
ToolTip:
import React from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show, onChange } = props;
return (
<>
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
onClick={onChange}
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
</>
);
}
export default Tooptip;
App
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect, useCallback } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const [onChangeTriggered, setonChangeTriggered] = useState(false);
const containerRef = useRef();
const itemClicked = useRef(false);
useEffect(() => {
containerRef.current.focus();
}, []);
return (
<div className="App">
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
debugger;
if (!onChangeTriggered) setShowTooltip(false);
}}
// onFocus={() => {
// setShowTooltip(true);
// }}
>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
if (!itemClicked.current) setShowTooltip((x) => !x);
itemClicked.current = false;
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList
show={showTooltip}
onChange={useCallback(() => {
itemClicked.current = true;
}, [])}
/>
</button>
</div>
</div>
);
}
https://codesandbox.io/s/epic-kapitsa-yh7si
Enjoy !!

Related

why is useEffect running on first render?

I have a MERN react component that is going to show a toast to my user after they create a new group.
Here is my useEffect below
useEffect(() => {
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
I only want this useEffect to run AFTER my user creates a new group.
It currently runs on first render, and after my user clicks the modal(child) then triggers the successCreate from redux(just creates a new group).
How can I make useEffect run only when successCreate is called.. NOT on first render AND successCreate
Here is my component
import React, { Fragment, useState, useEffect, useContext } from 'react';
import WizardInput from '../auth/wizard/WizardInput';
import {useDispatch, useSelector} from 'react-redux';
import {
Button,
Card,
CardBody,
Form,
Label,
Input,
Media,
Modal,
ModalBody,
ModalHeader
} from 'reactstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import Select from 'react-select';
import { toast } from 'react-toastify';
import makeAnimated from 'react-select/animated';
import {listGroups, createGroup} from '../../actions/index';
//import { isIterableArray } from '../../helpers/utils';
import { CsvUploadContext } from '../../context/Context';
import chroma from 'chroma'
const StepOneForm = ({ register, errors, watch}) => {
const { upload, setUpload } = useContext(CsvUploadContext);
//const { handleInputChange } = useContext(CsvUploadContext);
const [ createGroupModal, setCreateGroupModal ] = useState(false)
const [newGroup, setNewGroup] = useState({
title: ''
})
const dispatch = useDispatch();
const groups = useSelector(state => state.groups)
const groupCreate = useSelector((state) => state.groupCreate)
const {success: successCreate} = groupCreate
const animatedComponents = makeAnimated();
useEffect(() => {
dispatch(listGroups())
}, [successCreate])
useEffect(() => {
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
const customStyles = {
control: (base, state) => ({
...base,
background: "light",
// match with the menu
borderRadius: state.isFocused ? "3px 3px 0 0" : 3,
// Overwrittes the different states of border
borderColor: state.isFocused ? "primary" : "light",
// Removes weird border around container
boxShadow: state.isFocused ? null : null,
"&:hover": {
// Overwrittes the different states of border
borderColor: state.isFocused ? "blue" : "#2c7be5"
}
}),
menu: base => ({
...base,
// override border radius to match the box
borderRadius: 0,
// kill the gap
marginTop: 0
}),
menuList: base => ({
...base,
// kill the white space on first and last option
padding: 0,
color: 'f9fafd'
}),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
const color = chroma(data.color);
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? data.color
: isFocused,
color: '232e3c',
};
}
};
const toggle = () => { setCreateGroupModal(!createGroupModal)}
const closeBtn = (
<button className="close font-weight-normal" onClick={toggle}>
×
</button>
);
const handleSubmit = (e) => {
e.preventDefault()
dispatch(createGroup(newGroup))
setCreateGroupModal(false)
};
console.log(upload?.group)
const handleChange = e => {
setNewGroup({...newGroup, [e.target.name]: e.target.value})
}
return (
<Fragment>
<Media className="flex-center pb-3 d-block d-md-flex text-center mb-2">
<Media body className="ml-md-4">
</Media>
</Media>
<h4 className="mb-1 text-center">Choose Groups</h4>
<p className=" text-center fs-0">These groups will contain your contacts after import</p>
<Select
name="group"
required={true}
className="mb-3"
styles={customStyles}
components={animatedComponents}
innerRef={register({
required: true
})}
closeMenuOnSelect={true}
options={groups}
getOptionLabel={({title}) => title}
getOptionValue={({_id}) => _id}
onChange={(_id) => setUpload({...upload, group: _id})}
isMulti
placeholder="select group"
isSearchable={true}
errors={errors}
/>
<Button color="light" onClick={(() => setCreateGroupModal(true))} className="rounded-capsule shadow-none fs--1 ml- mb-0" >
<FontAwesomeIcon icon="user-plus" />
{` or create a new group`}
</Button>
<Modal isOpen={createGroupModal} centered toggle={() => setCreateGroupModal(!createGroupModal)}>
<ModalHeader toggle={toggle} className="bg-light d-flex flex-between-center border-bottom-0" close={closeBtn}>
Let's give the group a name
</ModalHeader>
<ModalBody className="p-0">
<Card>
<CardBody className="fs--1 font-weight-normal p-4">
<Card>
<CardBody className="fs--1 font-weight-normal p-4">
<Form onSubmit={handleSubmit}>
<Label for="title">Group Name:</Label>
<Input value={newGroup.title.value} onChange={handleChange} className="mb-3" name="title" id="title"/>
<Button block onClick={handleSubmit} color="primary" className="mb-3">Save</Button>
</Form>
</CardBody>
</Card>
<Button block onClick={() => setCreateGroupModal(false)}>close</Button>
</CardBody>
</Card>
</ModalBody>
</Modal>
<WizardInput
type="textarea"
label="or add number manually seperated by comma"
placeholder="+17209908576, +18165009878, +19138683784"
name="manual-add"
rows="4"
id="manual-add"
innerRef={register({
required: false
})}
errors={errors}
/>
</Fragment>
);
};
export default StepOneForm;
Is successCreate a boolean?
This would work:
useEffect(() => {
if(!successCreate) return;
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
useEffect is always called when the dependencies change - on first render it is guaranteed to be called because there is nothing previous - this is the equivalent to componentDidMount
useEffect(() => {
console.log('mounted');
},[]);

how to perform parent action from child in react

I have parent & its 2 child component
Parent code:
import React from 'react';
import { Grid, CircularProgress } from '#mui/material';
import { useSelector } from 'react-redux';
import Angle from './Angle/Angle';
import CustomSnackbar from '../Snackbar/CustomSnackbar';
import useStyles from './styles';
const Angles = ({ setCurrentId }) => {
const angles = useSelector((state) => state.angles);
const [snackbarOpenProp, setSnackbarOpenProp] = React.useState(false);
const classes = useStyles();
const handleSnackBarCloseAction = () => {
setSnackbarOpenProp(false)
}
const handleAddToCartAction = () => {
console.log('click');
setSnackbarOpenProp(true)
}
return (
!angles.length ? <CircularProgress /> : (
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
{angles.map((angle) => (
<Grid key={angle._id} item xs={12} sm={6} md={6}>
<Angle angle={angle} setCurrentId={setCurrentId} handleAddToCart={handleAddToCartAction}/>
</Grid>
))}
<CustomSnackbar openState={snackbarOpenProp} handleSnackBarCloseProp={handleSnackBarCloseAction}/>
</Grid>
)
);
};
export default Angles;
Child 1 code:
import React from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#mui/material';
import Add from '#mui/icons-material/Add';
import { useDispatch } from 'react-redux';
import useStyles from './styles';
const Angle = ({ angle, setCurrentId,handleAddToCart }) => {
const dispatch = useDispatch();
const classes = useStyles();
return (
<Card className={classes.card}>
<CardMedia className={classes.media} image={angle.image} />
<div className={classes.overlay}>
<Typography variant="h6">{angle.qualityName}</Typography>
<Typography variant="body2">{angle.colors}</Typography>
</div>
<div className={classes.overlay2}>
<Button onClick={handleAddToCart} style={{ color: 'white' }} size="small" onClick={() => setCurrentId(angle._id)}><Add fontSize="large" /></Button>
</div>
</Card>
);
};
export default Angle;
Child 2 code:
import * as React from 'react';
import Button from '#mui/material/Button';
import Snackbar from '#mui/material/Snackbar';
import IconButton from '#mui/material/IconButton';
import CloseIcon from '#mui/icons-material/Close';
export default function CustomSnackbar(props) {
const action = (
<React.Fragment>
<Button color="secondary" size="small" onClick={props.handleSnackBarCloseProp}>
UNDO
</Button>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={props.handleSnackBarCloseProp}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
);
return (
<div>
<Snackbar
open={props.openState}
autoHideDuration={6000}
onClose={props.handleSnackBarCloseProp}
message="Note archived"
action={action}
/>
</div>
);
}
As you can see, I am trying to attach the onClick event which is in Child1 Angle code & based on its click I am trying to change a state value of another child2 CustomSnackbar and sending it as a prop, but on clicking I am not getting a response, How can I do it, also is there any more simple way to achieve this ?
There are two onClick methods on the button component. Please change according below example and try it.
Before:
<Button onClick={handleAddToCart} style={{ color: 'white' }} size="small" onClick={() => setCurrentId(angle._id)}><Add fontSize="large" /></Button>
After:
<Button
style={{ color: 'white' }}
size="small"
onClick={() => {
handleAddToCart();
setCurrentId(angle._id)
}
}>
<Add fontSize="large" />
</Button>

can't render the products in proper order using nested map() & useEffect() in reactjs

I'm trying to build a restaurant web-app with Reactjs. I've parent categories, child categories, and products under child categories, I'm trying to build a UI with tabs where I can show the parent categories-clicked-show child categories & show the products under that child categories in proper order.
I'm getting the child categories when the parent is clicked, but I'm not getting the products as expected.
I want to render the product like this
Burger
Show all the burgers
Sandwich
show all the sandwich,
swarma
show all the sawrmas,
but instead im getting the swarmas only where i should get all the products in proper order.
Plz help me get the products in proper order.
This is my Menu.js
import React, { useEffect } from "react";
import MenuItems from "./MenuItems";
import "./testmenu.css";
// import burger from "../images/burger.jpg";
// import cake from "../images/cake-2.jpeg";
//Bootsrap
import { Container, Row, Col, Tab, Nav } from "react-bootstrap";
//REDUX
import { useSelector, useDispatch } from "react-redux";
import { getAllCategory } from "../actions";
import { Fragment } from "react";
const TestMenu = () => {
//GET ALL CATEGORIES WHEN RENDERED
const category = useSelector((state) => state.category);
console.log(category);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getAllCategory());
}, [dispatch]);
const renderParentCategories = (categories) => {
let parentCategories = [];
for (let category of categories) {
parentCategories.push(
<Nav.Item key={category.name}>
<Nav.Link eventKey={category.name}>{category.name}</Nav.Link>
</Nav.Item>
);
}
return parentCategories;
};
const renderChildrenCategories = (categories) => {
console.log(categories);
return categories.map((category, i) => (
<Tab.Pane key={i} eventKey={category.name}>
{category.children.map((cat, i) => (
<Fragment key={i}>
<h2>{cat.name}</h2>
<Row>{<MenuItems slug={cat.slug} />}</Row>
</Fragment>
))}
</Tab.Pane>
));
};
//CALLING THE LIST OF THE ITEMS
return (
<div className="Collections">
<Container>
<Tab.Container defaultActiveKey="first">
<Row>
<Col>
<Nav className="d-flex flex-row justify-content-center text-center">
{category.categories.length > 0
? renderParentCategories(category.categories)
: null}
</Nav>
</Col>
</Row>
<Row>
<Col>
<Tab.Content>
{category.categories.length > 0
? renderChildrenCategories(category.categories)
: null}
</Tab.Content>
</Col>
</Row>
</Tab.Container>
</Container>
</div>
);
};
export default TestMenu;
This is my MenuItems.js file:
import React, { useEffect } from "react";
import { Button, ButtonGroup, Col } from "react-bootstrap";
import { useDispatch, useSelector } from "react-redux";
import { addToCart, getProductsBySlug } from "../actions";
import { generatePublicUrl } from "../urlConfig";
const MenuItems = ({ slug }) => {
const product = useSelector((state) => state.product);
const dispatch = useDispatch();
console.log(product);
useEffect(() => {
dispatch(getProductsBySlug(slug));
}, [dispatch, slug]);
return product.products.map((product, i) => (
<Col key={i} xs={6} md={3} lg={3} xl={3}>
<div className="menuitem">
<div className="images">
<img
className="image"
src={generatePublicUrl(product.productPictures[0].img)}
alt="img"
/>
<div className="menuitem__buttons">
<ButtonGroup size="sm">
{/* onClick={props.itemInfo} */}
<Button variant="info">
<i className="fa fa-info-circle" aria-hidden="true" />
Info
</Button>
{/* onClick={props.addItem} */}
<Button
variant="success"
onClick={() => {
const { _id, name, price } = product;
const img = product.productPictures[0].img;
// console.log(_id, name, price, img);
dispatch(addToCart( _id, name, price, img ));
}}
>
<i className="fas fa-shopping-cart" />
Add
</Button>
</ButtonGroup>
</div>
<span className="images__h3">
<h5 style={{ textAlign: "center" }}>{product.name}</h5>
<h5
style={{
color: "green",
textAlign: "center",
fontSize: "16px",
}}
>
Price: {product.price}tk
</h5>
</span>
</div>
</div>
</Col>
));
};
export default MenuItems;
Here is the console:
Here is the result I'm getting:

findDOMNode is deprecated in StrictMode React material UI

I am getting this error on my React material UI project
By looking at the error I guess it comes somewhere inside the Drawer.js component.
This is my full Drawer.js component
import React, { Fragment, useState } from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import { List, Collapse } from "#material-ui/core";
import Divider from "#material-ui/core/Divider";
import ListItem from "#material-ui/core/ListItem";
import IconButton from "#material-ui/core/IconButton";
import ListItemText from "#material-ui/core/ListItemText";
import { toggleDrawer } from "../../store/actions/authActions";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
// import ClickAwayListener from '#material-ui/core/ClickAwayListener'
import { useHistory } from 'react-router-dom';
import { connect } from "react-redux";
import { withRouter } from "react-router";
const useStyles = makeStyles({
list: {
width: 250,
},
fullList: {
width: "auto",
},
});
function TemporaryDrawer(props) {
const classes = useStyles();
const theme = useTheme();
// const [open, setOpen] = React.useState(false);
const [openIndex, setOpenIndex] = useState(0);
let history = useHistory();
// const handleDrawerOpen = () => {
// setOpen(true);
// };
const handleDrawerClose = () => {
props.toggleDrawer();
};
const handleClick = (index) => {
if (openIndex === index) {
setOpenIndex(-1);
} else {
setOpenIndex(index);
}
};
const onToggle = () => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
props.toggleDrawer();
};
const onRoute = (path) => {
// props.history.push(path);
history.push(path);
props.toggleDrawer();
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === "top" || anchor === "bottom",
})}
role="presentation"
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{props.permissions.map((route, index) => (
<Fragment key={index}>
<ListItem button onClick={(e) => handleClick(index)}>
<ListItemText primary={route.name} />
{index === openIndex ? <ExpandLess /> : <ExpandMore />}
</ListItem>
{route.children.length && (
<Collapse
in={openIndex === index ? true : false}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
{route.children.map((child, idx) => (
<ListItem
button
className={classes.nested}
key={idx}
onClick={() => onRoute(child.path)}
>
<ListItemText primary={child.name} />
</ListItem>
))}
</List>
<Divider />
</Collapse>
)}
</Fragment>
))}
</List>
{/* <Divider /> */}
</div>
);
return (
<div>
{props.token && (
<Drawer
anchor="left"
open={props.isDrawerOpen}
onClose={onToggle("left", false)}
variant="persistent"
>
{list("left")}
</Drawer>
)}
</div>
);
}
const mapStateToProps = (state) => {
return {
isDrawerOpen: state.auth.isDrawerOpen,
token: state.auth.token,
permissions: state.auth.routes,
};
};
export default withRouter(
connect(mapStateToProps, { toggleDrawer })(TemporaryDrawer)
);
I go through this error and some say this is a problem in MUI library and no way to fix this. But I believe there must be a workaround for this. This causes serious problems for UI.
Where this error comes from and what can I do to fix this?
Any help!
Thanks in advance. =)
I use material UI 4.11.0
and react 16
You only need to create a new childComponent with react.forwardRef passing respective props and refs:
const ChildComponent = React.forwardRef((props, ref) =>
<div ref={ref}>
<yourChildcomponent {...props} />
</div>
);
In your render change the name of the original Child for the new:
<ChildComponent... />

Deleting a child component in react

So first, this web-app is one of my first web-apps using react, and I have done it using JavaScript and C# .net core.
What I want to do is, on click of a Button i want to creat a component, that appears on screen.
And so far that part is working well.
import FusePageSimple from '#fuse/core/FusePageSimple';
import { makeStyles } from '#material-ui/core/styles';
import Typography from '#material-ui/core/Typography';
import React, {ParentComponent, useEffect, useRef,useState } from 'react';
import { useDispatch } from 'react-redux';
import LeftSideBar from './components/SideBarComp';
import RightSideComp from './components/RightSideComp';
import Hidden from '#material-ui/core/Hidden';
import Icon from '#material-ui/core/Icon';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import { tokensToRegExp } from 'path-to-regexp';
const useStyles = makeStyles(theme => ({
layoutRoot: {}
}));
function Dashboard(props) {
const [compCount, setCompCount] = useState(0);
const [component, setComponent] = useState('');
const eventhandler = data => {
if(data.eliminar === 1){
setComponent(component.filter((item) => item.props.RowNum !== data.RowNum));
}
};
const classes = useStyles(props);
const pageLayout = useRef(null);
const onAddChild = () => {
setCompCount(compCount + 1);
}
useEffect(() => {
const compArry = [...component];
if(compCount > 0 ){
compArry.push(<RightSideComp key={compCount} RowNum={compCount} onChange={eventhandler}/>)
}
setComponent(compArry);
}, [compCount]);
return (
<FusePageSimple
classes={{
root: classes.layoutRoot
}}
header={
<div className="flex flex-col flex-1">
<div className="flex items-center p-24 px-12">
<Hidden lgUp>
<IconButton
onClick={ev => pageLayout.current.toggleLeftSidebar()}
aria-label="open left sidebar"
>
<Icon>menu</Icon>
</IconButton>
</Hidden>
<div className="flex-1 lg:px-12">
<h4>Header</h4>
</div>
</div>
</div>
}
contentToolbar={
<div className="px-24">
<h4>Content Toolbar</h4>
</div>
}
content={
<div className="p-24">
<h4>Content</h4>
<br />
{component}
</div>
}
leftSidebarHeader={
<div className="p-24">
<h4>Sidebar Header</h4>
</div>
}
leftSidebarContent={
<div className="p-24">
<Button variant="contained" color="primary"
onClick={ev => onAddChild()}>
Novo
</Button>
{/* <Button variant="contained" color="primary">Copiar</Button> */}
<br />
<LeftSideBar />
</div>
}
innerScroll
ref={pageLayout}
/>
);
}
export default Dashboard;
Thats the code of the parent.
So by clicking the button "Novo"(Means "New" in english) I call the onAddChild(), I add To the compCount and after with the UseEffect I add the child component. I pass 2 props to that Child RowNum, the number of components that I am creating, and an event that is trigger on state change.
import React,{ useEffect, useState } from 'react';
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import TextField from '#material-ui/core/TextField';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Divider from '#material-ui/core/Divider';
const useStyles = makeStyles(theme => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(2),
width: '25ch'
}
}
}));
function RightSideComp(props) {
const classes = useStyles();
const [component, setComponent] = useState({
dataInicio: (!(props.datastate) ? '' : props.datastate.dataInicio),
dataFim: (!(props.datastate) ? '' : props.datastate.dataFim),
descricao: (!(props.datastate) ? '' : props.datastate.descricao),
RowNum: props.RowNum,
eliminar: 0,
copiar: 0
});
console.log(component);
const handleChangeInput = event => {
const { name, value } = event.target;
setComponent(prevState => ({
...prevState,
[name]: value
}));
};
const handleChangeButtonCopiar = event => {
setComponent(preState => ({ ...preState, copiar: 1 }));
};
const handleChangeButtonEliminar = event => {
setComponent(preState => ({ ...preState, eliminar: 1 }));
};
useEffect(() => {
props.onChange(component);
}, [component]);
return (
<div>
<div className={classes.root}>
<TextField
id="dataInicio"
label="Data Inicio"
name="dataInicio"
type="datetime-local"
className={classes.textField}
value={component.dataInicio}
onChange={ev => handleChangeInput(ev)}
InputLabelProps={{
shrink: true
}}
/>
<TextField
id="dataFim"
name="dataFim"
label="Data do Fim"
type="datetime-local"
className={classes.textField}
value={component.dataFim}
onChange={ev => handleChangeInput(ev)}
InputLabelProps={{
shrink: true
}}
/>
<TextField
id="outlined-multiline-flexible"
label="Multiline"
name="descricao"
multiline
rowsMax={5}
value={component.descricao}
onChange={ev => handleChangeInput(ev)}
variant="outlined"
/>
<div>
<Button variant="contained" color="primary" onClick={ev => handleChangeButtonCopiar(ev)}>
Copiar
</Button>
<Button variant="contained" color="secondary" onClick={ev => handleChangeButtonEliminar(ev)}>
Eliminar
</Button>
</div>
</div>
<Divider />
</div>
);
}
export default React.memo(RightSideComp);
and thats my child component.
So my error is when I change the eliminar("delete" in english) that component gets deleted. And with the code I have, if it is the last row it deletes fine but, if it is one from the middle it delets that one and all the others that are below the one I clicked. I tried with slice and didnt work well too. I am missing something, but what?
thanks for your time
I think you need to use 'useRef'
const refsList = React.useRef([]);
then when you create something
compArry.push(<RightSideComp ref={refsList[compCount]} key={compCount} RowNum={compCount} onChange={eventhandler}/>)
now you can directly do anything you want with any elements based on this ref, eg. refsList[0].current will be the actual 1st element
refsList[0].current.remove()

Categories