I am trying to achieve the following behaviour. On click of a dropdown a tree should get displayed as an option, and whatever operation on the tree, should keep the dropdown open. It should only get closed whenever the dropdown is clicked again and also on outer click.I am using onClick, onClose and onOpen handlers. But somehow onclick and onclose are conflicting. Can someone help me how to achieve this?
Sandbox: https://codesandbox.io/s/semantic-ui-react-so-yzemk?file=/index.js
import React from "react";
import { render } from "react-dom";
import { Dropdown } from "semantic-ui-react";
import CheckboxTree from "react-checkbox-tree";
import "react-checkbox-tree/lib/react-checkbox-tree.css";
const nodes = [
{
value: "mars",
label: "Mars",
children: [
{ value: "phobos", label: "Phobos" },
{ value: "deimos", label: "Deimos" }
]
}
];
class App extends React.Component {
state = {
checked: [],
expanded: [],
options: [],
open: false
};
onClose = e => {
console.log("on close");
this.setState({ open: true });
};
onOpen = e => {
console.log("on open");
this.setState({ open: true });
};
onChange = e => {
console.log("on change");
e.stopPropagation();
this.setState({ open: true });
};
onClick = e => {
console.log("on click");
e.stopPropagation();
this.setState({ open: !this.state.open });
};
render() {
return (
<div>
<Dropdown
className="icon"
selection
options={this.state.options}
text="Select"
open={this.state.open}
onClose={this.onClose}
onOpen={this.onOpen}
onChange={this.onChange}
onClick={this.onClick}
>
<Dropdown.Menu>
<Dropdown.Item>
<CheckboxTree
nodes={nodes}
checked={this.state.checked}
expanded={this.state.expanded}
onCheck={checked =>
this.setState({ checked }, () => {
console.log(this.state.checked);
})
}
onExpand={expanded => this.setState({ expanded })}
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
);
}
}
You have a bug in the onClose function. change it to this:
onClose = e => {
console.log("on close");
this.setState({ open: false });
};
----EDIT----
After reading a bit I figured out the problem:
First - you don't need to implement all these onClick, onOpen, onClose functions.
You simply need to add stopPropagation() on the Dropdown.Item.
Check out this code - let me know if that works for you:
import React from "react";
import { render } from "react-dom";
import { Dropdown } from "semantic-ui-react";
import CheckboxTree from "react-checkbox-tree";
import "react-checkbox-tree/lib/react-checkbox-tree.css";
const nodes = [
{
value: "mars",
label: "Mars",
children: [
{ value: "phobos", label: "Phobos" },
{ value: "deimos", label: "Deimos" }
]
}
];
class App extends React.Component {
state = {
checked: [],
expanded: [],
options: [],
open: false
};
render() {
return (
<div>
<Dropdown
className="icon"
selection
text="Select"
>
<Dropdown.Menu>
<Dropdown.Item onClick={(e) => e.stopPropagation()}>
<CheckboxTree
nodes={nodes}
checked={this.state.checked}
expanded={this.state.expanded}
onCheck={checked =>
this.setState({ checked }, () => {
console.log(this.state.checked);
})
}
onExpand={expanded => this.setState({ expanded })}
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Related
I have multiple Select fields in my App component. Since it throws an error if I pass the event object(to access 'id' of each component) as a second parameter to the event handler(along with selectedOptions), I cannot have a single event handler for all the Select components. Instead, I currently need a separate event handler for each component, so that I can store the selected options of each component in different arrays in component state. If I could use event.target.id, I could do the same job with just one function.
codesandbox: https://codesandbox.io/s/peaceful-spence-1j3nv?fontsize=14&hidenavigation=1&theme=dark&file=/src/App.js
import React from 'react'
import Select from 'react-select';
const options = [
{ value: 'red', label: 'red' },
{ value: 'blue', label: 'blue' },
{ value: 'green', label: 'green' }
];
class App extends React.Component {
state = {
selectedOptionsFIRST: [],
selectedOptionsSECOND: [],
}
handleChangeFIRST = (selectedOptions) => {
this.setState({ selectedOptionsFIRST: selectedOptions });
}
handleChangeSECOND = (selectedOptions) => {
this.setState({ selectedOptionsSECOND: selectedOptions });
}
render() {
return (
<React.Fragment>
<Select
id={0}
isMulti
value={this.state.selectedOptionsFIRST}
onChange={this.handleChangeFIRST}
options={options}
/>
{this.state.selectedOptionsFIRST.map(o => <p>{o.value}</p>)}
<Select
id={1}
isMulti
value={this.state.selectedOptionsSECOND}
onChange={this.handleChangeSECOND}
options={options}
/>
{this.state.selectedOptionsSECOND.map(o => <p>{o.value}</p>)}
</React.Fragment>
);
}
}
What about passing the state field in the handler?
import React from "react";
import Select from "react-select";
const options = [
{ value: "red", label: "red" },
{ value: "blue", label: "blue" },
{ value: "green", label: "green" }
];
class App extends React.Component {
state = {
selectedOptionsFIRST: [],
selectedOptionsSECOND: []
};
handleChange = (id, selectedOptions) => {
this.setState({ [id]: selectedOptions });
};
render() {
return (
<React.Fragment>
<Select
isMulti
value={this.state.selectedOptionsFIRST}
onChange={(e) => {this.handleChange('selectedOptionsFIRST',e);}}
options={options}
/>
{this.state.selectedOptionsFIRST.map((o) => (
<p>{o.value}</p>
))}
<Select
isMulti
value={this.state.selectedOptionsSECOND}
onChange={(e) => {this.handleChange('selectedOptionsSECOND',e);}}
options={options}
/>
{this.state.selectedOptionsSECOND.map((o) => (
<p>{o.value}</p>
))}
</React.Fragment>
);
}
}
export default App;
I have a dynamic navigation removable tabs using Fluent for react
I would like that when I close a tab ( for example test3) , the focus gets on the last tab in the nav bar like bellow
my actual problem is that when I close a tab , I loose the focus.
Here's my code
import React from "react";
import { Button, Menu, tabListBehavior } from "#fluentui/react-northstar";
import { CloseIcon } from "#fluentui/react-icons-northstar";
class MenuExampleTabShorthand extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
items = [
{
key: "editorials",
content: (
<div>
"test"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("editorials")}
/>
</div>
)
},
{
key: "review",
content: (
<div>
"test2"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("review")}
/>
</div>
)
},
{
key: "events",
content: (
<div>
"test3"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("events")}
/>
</div>
)
}
];
closeClick = task => {
this.setState(function(prev, props) { // Im setting the selectedIndex to 0
return { ...prev, selectedIndex:0 };
});
this.items = this.items.filter(elm => elm.key !== task);
};
render() {
return (
<Menu
activeIndex={this.state.selectedIndex}
onActiveIndexChange={(i, j) => {
this.setState(function(prev, props) {
return { ...prev, selectedIndex: j.activeIndex };
});
}}
items={this.items}
underlined
primary
accessibility={tabListBehavior}
aria-label="Today's events"
/>
);
}
}
export default MenuExampleTabShorthand;
Here's a reproduction of error demo
The issue you are facing is caused by event propagation, you can fix it by adding e.stopPropagation(); in close click event handler, and not having it will cause the active item click handler to fire and then set the current active item to the one removed (codesandbox), note that I'm passing the event object to closeClick:
import React from "react";
import { Button, Menu, tabListBehavior } from "#fluentui/react-northstar";
import { CloseIcon } from "#fluentui/react-icons-northstar";
class MenuExampleTabShorthand extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
items = [
{
key: "editorials",
content: (
<div>
"test"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("editorials", e)}
/>
</div>
)
},
{
key: "review",
content: (
<div>
"test2"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("review", e)}
/>
</div>
)
},
{
key: "events",
content: (
<div>
"test3"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("events", e)}
/>
</div>
)
}
];
closeClick = (task, e) => {
e.stopPropagation();
this.setState(function(prev, props) {
return { ...prev, selectedIndex: 0 };
});
console.log(this.items);
this.items = this.items.filter(elm => elm.key !== task);
console.log(this.items);
};
render() {
return (
<Menu
activeIndex={this.state.selectedIndex}
onActiveIndexChange={(i, j) => {
this.setState(function(prev, props) {
return { ...prev, selectedIndex: j.activeIndex };
});
}}
items={this.items}
underlined
primary
accessibility={tabListBehavior}
aria-label="Today's events"
/>
);
}
}
export default MenuExampleTabShorthand;
I am trying to bring popup based on clicking of the respective links. Here I could see onClick is being triggered along with the state change But no modal is appearing.
Can some one tell me what is it that I am doing wrong. I am using semantic-ui-react modal for the same puropse
Sandbox: https://codesandbox.io/s/semantic-ui-example-seg91?file=/Modal.js
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true });
return (
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false });
}}
items={value}
/>
);
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
</>
);
}
}
export default LoaderExampleText;
import * as React from "react";
import { Modal } from "semantic-ui-react";
class NestedTableViewer extends React.Component {
render() {
return (
<>
<Modal closeIcon={true} open={this.props.modalOpen}>
<Modal.Header>Modal</Modal.Header>
<Modal.Content>
<h1> {this.props.items}</h1>
</Modal.Content>
</Modal>
</>
);
}
}
export default NestedTableViewer;
Save the value into state and move the modal into the render return. All renderable JSX needs to be returned as a single node tree.
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
value: null
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true, value });
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false, value: null });
}}
items={this.state.value}
/>
</>
);
}
}
export default LoaderExampleText;
In setModal is a function , so you cannot do some think like that, but if you want return you must add in render {setModal}.
So in this case :
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
value: ""
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true });
this.setState({ value: value });
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false });
}}
items={this.state.value}
/>
</>
);
}
}
export default LoaderExampleText;
it will work;
I have a situation where in I am having multiple on click events that are fired on a column header. There will be one event for filter dropdown and the other one for sorting. There is a filter icon , on click of which column filter options will be shown. And on click of the header, sorting should also happen.
Now whenever I click on the filter icon, both handlers are getting fired. Can someone help me with this.
On click of filter icon, only filter handler should fire
Help would be appreciated.
Sandbox: https://codesandbox.io/s/relaxed-feather-xrpkp
Parent
import * as React from "react";
import { render } from "react-dom";
import ReactTable from "react-table";
import "./styles.css";
import "react-table/react-table.css";
import Child from "./Child";
interface IState {
data: {}[];
columns: {}[];
}
interface IProps {}
export default class App extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
data: [
{ firstName: "Jack", status: "Submitted", age: "14" },
{ firstName: "Simon", status: "Pending", age: "15" },
{ firstName: "Pete", status: "Approved", age: "17" }
],
columns: []
};
}
handleColumnFilter = (value: any) => {
console.log(value);
};
sortHandler = () => {
console.log("sort handler");
};
componentDidMount() {
let columns = [
{
Header: () => (
<div onClick={this.sortHandler}>
<div style={{ position: "absolute", marginLeft: "10px" }}>
<Child handleFilter={this.handleColumnFilter} />
</div>
<span>First Name</span>
</div>
),
accessor: "firstName",
sortable: false,
show: true,
displayValue: " First Name"
},
{
Header: () => (
<div onClick={this.sortHandler}>
<span>Status</span>
</div>
),
accessor: "status",
sortable: false
}
];
this.setState({ columns });
}
render() {
const { data, columns } = this.state;
return (
<div>
<ReactTable
data={data}
columns={columns}
defaultPageSize={10}
className="-striped -highlight"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Filter Component
import * as React from "react";
import { Icon } from "semantic-ui-react";
import "./styles.css";
interface IProps {
handleFilter(val1: any): void;
}
interface IState {
showList: boolean;
}
export default class Child extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
showList: false
};
}
toggleList = () => {
console.log("filter handler");
this.setState(prevState => ({ showList: !prevState.showList }));
};
render() {
let { showList } = this.state;
let visibleFlag: string;
if (showList === true) visibleFlag = "visible";
else visibleFlag = "";
return (
<div>
<div style={{ position: "absolute" }}>
<div
className={"ui scrolling dropdown column-settings " + visibleFlag}
>
<Icon className="filter" onClick={this.toggleList} />
</div>
</div>
</div>
);
}
}
You just need event.stopPropagation(). This will isolate the event to only this specific execution-block. So now when you click on the filter-icon, it will only trigger the designated event-handler.
toggleList = (event) => {
event.stopPropgation()
console.log("filter handler");
this.setState(prevState => ({ showList: !prevState.showList }));
};
You'll also need to use it here as well:
handleValueChange = (event: React.FormEvent<HTMLInputElement>, data: any) => {
event.stopPropagation()
let updated: any;
if (data.checked) {
updated = [...this.state.selected, data.name];
} else {
updated = this.state.selected.filter(v => v !== data.name);
}
this.setState({ selected: updated });
};
And here:
passSelectionToParent = (event: any) => {
event.preventDefault();
event.stopPropagation()
this.props.handleFilter(this.props.name, this.state.selected);
};
Literally, anytime you have a click-event for a parent-markup and it has children mark-up that also have a click-event, you can use event.stopPropagation() to stop the parent click-event from firing.
Here's the sandbox: https://codesandbox.io/s/elated-gauss-wgf3t
I am using ReactJS and a Bootstrap modal. I can open the modal just fine, but I would like it to close after 3 seconds.
I tried setTimeout as you can see below, but it doesn't close. I gave setTimeout a callback of handleClose, but after console logging, I can see that handleClose is not being called.
Here is the ItemDetailView Component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Card, CardImg, CardText, CardBody,
CardTitle, CardSubtitle } from 'reactstrap';
import { addToCart } from '../actions/addToCartAction';
import './ItemDetailView.css';
import ItemAddedModal from './ItemAddedModal';
class ItemDetailView extends Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
}
// this.toggle = this.toggle.bind(this);
};
// toggle() {
// this.setState({
// modalOpen: !this.state.modalOpen
// });
// };
handleOpen = () => {
console.log("Cart Open", this.state.modalOpen);
this.setState({
modalOpen: true
},() => {setTimeout(this.handleClose(), 3000)});
// setTimeout(this.handleClose(), 3000);
};
handleClose = () => {
this.setState({
modalOpen: false
});
console.log('handleClose fired!')
};
addToCartHandler = () => {
this.props.addToCart(this.props.card);
console.log('addToCart++', this.props.quantity);
this.handleOpen()
// this.setState({
// modalOpen: true
// });
};
render() {
if (!this.props.title) {
return null;
}
return (
<div className="detail-view-wrapper">
<Card className="text-center detail-view-card">
{/* <CardImg top width="100%" src={"/" + this.props.img} alt={this.props.title} /> */}
<CardImg className="detail-view-img" top width="100%" src={"/" + this.props.img} alt={this.props.title} />
<CardBody>
<CardTitle className={"card-title"}>{this.props.title}</CardTitle>
<CardSubtitle>${this.props.price}</CardSubtitle>
<CardText>{this.props.description}</CardText>
{/* <SvgIcon className="cart-icon" onClick={() => this.addToCartHandler()} >
<AddShoppingCart />
</SvgIcon> */}
<button className= "add-to-cart-button" onClick={() => this.addToCartHandler()}>Add To Cart</button>
</CardBody>
</Card>
<ItemAddedModal open={this.state.modalOpen} toggle={this.toggle} />
</div>
);
}
}
const mapStateToProps = state => {
if (!state.data.cardData) {
return {
title: null,
img: null,
description: null,
price: null
}
}
const card = state.data.cardData[state.card.id]
return {
card: card,
title: card.title,
id: card.id,
img: card.img,
description: card.description,
price: card.price,
quantity: 0
};
}
export default connect(mapStateToProps, { addToCart })(ItemDetailView);
Here is the ItemAddedModal:
import React from 'react';
import { Modal, ModalHeader } from 'reactstrap';
import './ItemAddedModal.css';
class ItemAddedModal extends React.Component {
render () {
return (
<div>
<Modal className="item-added-modal" isOpen={this.props.open} toggle={this.props.toggle} className={this.props.className}>
<ModalHeader className="item-added-modal-header">
<p className="item-added-modal-p">Item Added To Cart</p>
</ModalHeader>
</Modal>
</div>
)
};
}
export default ItemAddedModal;
To perform an action after a state is set, we need to pass a callback to setState.
this.setState({
modalOpen: true
},()=>{
console.log(this.state.modalOpen);});