Deleting a child component in react - javascript

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()

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>

React - how to click outside to close the tooltip

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 !!

Why my dispatch is not working for button click event?

I have built a Trello clone using ReactJS, where I have 4 columns called TODO, DOING, DONE and REJECTED, where I can add a card to any column.
In a file I am trying to map over card component and rendering properties from defined dummy data.
What I want to do?
I want to delete a specific card when the button is clicked.
What I tried?
I have added the functionality in my Redux store, but when adding the onclick event to my button, I cannot access the dispatch method which will trigger the deleteCard function.
How do I do that?
My TaskboardList.js component :
import React from "react";
import TaskboardCard from "./TaskboardCard";
import TaskboardActionButton from "./TaskboardActionButton";
import { Droppable } from "react-beautiful-dnd";
const TaskboardList = ({ title, cards, listID }) => {
return (
<Droppable droppableId={String(listID)}>
{provided => (
<div
className="taskboardlist_container"
{...provided.droppableProps}
ref={provided.innerRef}
style={styles.container}
>
<div className="sub-heading">{title}</div>
{cards.map((card, index) => (
<TaskboardCard
key={card.id}
index={index}
text={card.text}
id={card.id}
/>
))}
<TaskboardActionButton listID={listID} />
{provided.placeholder}
</div>
)}
</Droppable>
);
};
const styles = {
container: {
backgroundColor: "#eee",
width: 300,
padding: "0.5rem",
marginRight: "1rem",
height: "100%"
}
};
export default TaskboardList;
My TaskboardCard.js component
import React from "react";
import Card from "#material-ui/core/Card";
import Typography from "#material-ui/core/Typography";
import CardContent from "#material-ui/core/CardContent";
import { Draggable } from "react-beautiful-dnd";
import { connect } from "react-redux";
import { deleteCard } from "../actions";
const TaskboardCard = ({ text, id, index, sample, cardId }) => {
// handleClickDelete = () => {
// // const { dispatch } = this.props;
// // dispatch(deleteCard(cardId));
// console.log("clicked");
// };
return (
<Draggable draggableId={String(id)} index={index}>
{provided => (
<div
className="taskboard_container"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card>
<CardContent>
<Typography style={{ fontSize: "1.5rem" }} gutterBottom>
{text}
</Typography>
</CardContent>
</Card>
{/* //delete added */}
<button
onClick={(cardId, props, sample, dispatch) => {
//const { dispatch } = this.props;
dispatch(deleteCard(cardId));
}}
>
DELETE
</button>
{/* ////////////////////// */}
</div>
)}
</Draggable>
);
};
export default connect()(TaskboardCard);
In the above component delete button is not working because somehow i cannot access the dispatch.
Here is my codesandbox link for further reference to files https://codesandbox.io/s/github/abhinav-anshul/consensolabs
Remove the props and dispatch from onclick event and add dispatch in component parameter list.
If you don't specify the second argument to connect(), your component will receive dispatch by default in porps.
import React from "react";
import Card from "#material-ui/core/Card";
import Typography from "#material-ui/core/Typography";
import CardContent from "#material-ui/core/CardContent";
import { Draggable } from "react-beautiful-dnd";
import { connect } from "react-redux";
import { deleteCard } from "../actions";
const TaskboardCard = ({ text, id, index, sample, cardId, dispatch }) => {
// handleClickDelete = () => {
// // const { dispatch } = this.props;
// // dispatch(deleteCard(cardId));
// console.log("clicked");
// };
return (
<Draggable draggableId={String(id)} index={index}>
{provided => (
<div
className="taskboard_container"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card>
<CardContent>
<Typography style={{ fontSize: "1.5rem" }} gutterBottom>
{text}
</Typography>
</CardContent>
</Card>
{/* //delete added */}
<button
onClick={(cardId, sample) => {
//const { dispatch } = this.props;
dispatch(deleteCard(cardId));
}}
>
DELETE
</button>
{/* ////////////////////// */}
</div>
)}
</Draggable>
);
};
export default connect()(TaskboardCard);

Not able to copy to clipboard from Popper which is inside Dialogue in material-UI

Steps to reproduce the bug here: (Try to open in Firefox, I was almost crashed the chrome :P) https://codesandbox.io/s/73z5293391
Click on the OPEN SIMPLE DIALOGUE button. Now you see a dialogue, Click on the TOGGLE POPPER button.
Now you see a Popper which has an input box and a COPY button.
You need to copy on the text inside the input box in this case hello.
So I am not able to copy to clipboard actually.
First I thought it might be a problem with Dialogue. But no. In just a Dialogue it works. But not on Popper which pops up from the Dialogue(Only for Popper also it works).
Can you help me to copy to clipboard in this situation?
Once again the source code:
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import Avatar from "#material-ui/core/Avatar";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemAvatar from "#material-ui/core/ListItemAvatar";
import ListItemText from "#material-ui/core/ListItemText";
import DialogTitle from "#material-ui/core/DialogTitle";
import Dialog from "#material-ui/core/Dialog";
import PersonIcon from "#material-ui/icons/Person";
import AddIcon from "#material-ui/icons/Add";
import Typography from "#material-ui/core/Typography";
import blue from "#material-ui/core/colors/blue";
import DialogContent from "#material-ui/core/DialogContent";
import Popper from "#material-ui/core/Popper";
const emails = ["username#gmail.com", "user02#gmail.com"];
const styles = {
avatar: {
backgroundColor: blue[100],
color: blue[600]
}
};
class SimpleDialog extends React.Component {
state = {
anchorEl: null,
openPopper: false
};
handleClose = () => {
this.props.onClose(this.props.selectedValue);
};
handleListItemClick = value => {
this.props.onClose(value);
};
copytoClipBoard = () => {
this.hello.select();
try {
return document.execCommand("copy");
} catch (err) {
console.log("Oops, unable to copy");
}
};
handleClick = event => {
const { currentTarget } = event;
this.setState(state => ({
anchorEl: currentTarget,
openPopper: !state.openPopper
}));
};
render() {
const { classes, onClose, selectedValue, ...other } = this.props;
const { anchorEl, openPopper } = this.state;
const id = openPopper ? "simple-popper" : null;
return (
<Dialog
onClose={this.handleClose}
aria-labelledby="simple-dialog-title"
{...other}
>
<DialogTitle id="simple-dialog-title">Set backup account</DialogTitle>
<DialogContent>
<Button
aria-describedby={id}
variant="contained"
onClick={this.handleClick}
>
Toggle Popper
</Button>
<Popper
id={id}
open={openPopper}
anchorEl={anchorEl}
style={{ zIndex: 10000 }}
>
<input
value="hello"
readOnly
type="text"
ref={node => (this.hello = node)}
/>
<Button onClick={this.copytoClipBoard}> Copy </Button>
</Popper>
<List>
{emails.map(email => (
<ListItem
button
onClick={() => this.handleListItemClick(email)}
key={email}
>
<ListItemAvatar>
<Avatar className={classes.avatar}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItem>
))}
<ListItem
button
onClick={() => this.handleListItemClick("addAccount")}
>
<ListItemAvatar>
<Avatar>
<AddIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="add account" />
</ListItem>
</List>
</DialogContent>
</Dialog>
);
}
}
SimpleDialog.propTypes = {
classes: PropTypes.object.isRequired,
onClose: PropTypes.func,
selectedValue: PropTypes.string
};
const SimpleDialogWrapped = withStyles(styles)(SimpleDialog);
class SimpleDialogDemo extends React.Component {
state = {
open: false,
selectedValue: emails[1]
};
handleClickOpen = () => {
this.setState({
open: true
});
};
handleClose = value => {
this.setState({ selectedValue: value, open: false });
};
render() {
return (
<div>
<Typography variant="subtitle1">
Selected: {this.state.selectedValue}
</Typography>
<br />
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
Open simple dialog
</Button>
<SimpleDialogWrapped
selectedValue={this.state.selectedValue}
open={this.state.open}
onClose={this.handleClose}
/>
</div>
);
}
}
export default SimpleDialogDemo;
#akhila-hegde You can add disablePortal to the Popper to solve this.
Note that problem is not with Copy to clipboard. Problem is that you are not able to select the text in the field (because it is in a Portal).
Here is Sandbox link with disablePortal set to true - https://codesandbox.io/s/lxjwj3p8m9

Categories