I have created a react component tree for navigation.
You can see it working in the below codepen.
React Component Tree
let navConfigData = {
isDashboardVisible: true,
tierOneLinks: [
{
name: "First Page",
iconName: "star",
iconAbbreviation: "FP",
tierTwoItems: [
{
name: "Tier 2 Page",
iconName: "star",
iconAbbreviation: "FP",
tierThreeItems: [
{
name: "Tier 3 Page",
iconName: "star",
iconAbbreviation: "FP",
path: "#"
}
]
},
{
name: "Tier 2 sec Page",
iconName: "star",
iconAbbreviation: "FP",
path: "#"
}
]
},
{
name: "Second Page",
iconName: "",
iconAbbreviation: "SP",
path: "#"
},
{
name: "Third Page",
iconAbbreviation: "TP",
path: "#",
tierTwoItems: [
{
name: "Tier 2 Page",
iconName: "star",
iconAbbreviation: "FP",
tierThreeItems: [
{
name: "Tier 3 Page",
iconName: "star",
iconAbbreviation: "FP",
path: "#"
}
]
},
{
name: "Tier 2 sec Page",
iconName: "star",
iconAbbreviation: "FP",
path: "#"
}
]
},
{
name: "Fourth Page",
iconName: "send",
iconAbbreviation: "FO",
path: "#"
}
],
supportLinks: [
{
name: "First Support Page",
iconName: "settings",
iconAbbreviation: "FI",
path: "#"
},
{
name: "Second Support Page",
iconName: "security",
iconAbbreviation: "SE",
path: "#"
},
{
name: "Third Support Page",
iconName: "help",
iconAbbreviation: "TH",
path: "#"
}
]
};
class NavigationDrawerTierThree extends React.Component {
onClick = evt => {
this.props.onSelect(this.props.tierThree.name);
};
render() {
const { tierThree, selectedTierThreeLink } = this.props;
debugger;
return (
<a
className="mdc-list-item my-nav-drawer__tier-three"
onClick={this.onClick}
>
<span className="my-nav-drawer__tier-two-label">
{tierThree.name}
</span>
</a>
);
}
}
class NavigationDrawerTierTwo extends React.Component {
// static propTypes = {
// tierTwo: object.isRequired,
// showNavIcon: bool,
// onSelect: func.isRequired,
// selectedTierTwoLink: string.isRequired
// };
state = { tierTwoIsExpanded: false };
onClick = evt => {
debugger;
this.props.onSelect(this.props.tierTwo.name, "tier2");
const { tierTwoIsExpanded } = this.state;
this.setState({ tierTwoIsExpanded: !tierTwoIsExpanded });
};
getArrowIcon = () => {
return this.state.tierTwoIsExpanded ? (
<span class="material-icons cdk-nav-drawer__tier-two-icon">></span>
) : (
<span class="material-icons cdk-nav-drawer__tier-two-icon">
^
</span>
);
};
getAnchorClassBasedOnTier = () => {
return this.props.showNavIcon
? "cdk-nav-drawer__tier-two"
: "cdk-nav-drawer__tier-three";
};
getLabelClassBasedOnTier = () => {
return this.props.showNavIcon
? "cdk-nav-drawer__tier-two-label"
: "cdk-nav-drawer__tier-three-label";
};
render() {
const { tierTwo, showNavIcon, selectedTierTwoLink } = this.props;
const { tierTwoIsExpanded } = this.state;
debugger;
return (
<div>
<a
className="mdc-list-item my-nav-drawer__tier-two"
onClick={this.onClick}
>
{showNavIcon && this.getArrowIcon()}
<span className="my-nav-drawer__tier-two-label">
{tierTwo.name}
</span>
</a>
{tierTwoIsExpanded && this.props.children}
</div>
);
}
}
class NavigationDrawerTierOneAnchor extends React.Component{
// static propTypes = {
// tierOne: object.isRequired,
// onSelect: func.isRequired,
// selectedTierOneLink: string.isRequired
// };
// state = { tierIsExpanded: false };
onClick = evt => {
this.props.onSelect(this.props.tierOne.name, "tier1");
// const { tierIsExpanded } = this.state;
// this.setState({ tierIsExpanded: !tierIsExpanded });
};
render(){
const { tierOne, selectedTierOneLink, expanded } = this.props;
return(
<div>
<a
className="mdc-list-item my-nav-drawer__tier-one"
href={tierOne.path}
onClick={this.onClick}
ref={a => {
this.anchor = a;
}}
>
<span className="cdk-nav-drawer__tier-one-label">
{tierOne.name}
</span>
</a>
{expanded && this.props.children}
</div>
);
}
}
class NavigationDrawerAppTiers extends React.Component {
// static propTypes = {
// tierOneArray: array.isRequired,
// onToggleExpand: func.isRequired,
// onSelect: func.isRequired,
// selectedTierOneLink: string.isRequired,
// selectedTierTwoLink: string.isRequired,
// selectedTierThreeLink: string.isRequired,
// expandedTierOneName: string.isRequired
// };
render(){
const { tierOneArray, onSelect, selectedTierOneLink, selectedTierTwoLink, selectedTierThreeLink, expandedTierOneName } = this.props;
return (
<div className="mdc-drawer__content my-nav-drawer__app-tiers">
<nav className="mdc-list">
{tierOneArray.map((tierOneLink, index) => (
<NavigationDrawerTierOneAnchor
tierOne={tierOneLink}
onSelect={onSelect}
key={index}
expanded={tierOneLink.name === this.props.expandedTierOneName}
selectedTierOneLink={selectedTierOneLink}
>
{(tierOneLink.tierTwoItems || []).map((tierTwoItem, index) => (
<NavigationDrawerTierTwo
tierTwo={tierTwoItem}
onSelect={onSelect}
selectedTierTwoLink={selectedTierTwoLink}
showNavIcon={
(tierTwoItem.tierThreeItems &&
tierTwoItem.tierThreeItems.length > 0) ||
false
}
>
{(tierTwoItem.tierThreeItems || []).map(
(tierThreeItem, index) => (
<NavigationDrawerTierThree
tierThree={tierThreeItem}
onSelect={onSelect}
selectedTierThreeLink={selectedTierThreeLink}
/>
)
)}
</NavigationDrawerTierTwo>
))}
</NavigationDrawerTierOneAnchor>
))}
</nav>
</div>
);
}
}
class NavigationDrawer extends React.Component {
// static propTypes = {
// navConfig: object.isRequired
// };
state = {
isDrawerCollapsed: false,
selectedTierOneLink: "",
selectedTierTwoLink: "",
selectedTierThreeLink: "",
expandedTierOneName: ""
};
onSelect = (selectedLinkName, selectedTier) => {
if(selectedTier == "tier1"){
if (selectedLinkName === this.state.selectedTierOneLink) {
// User clicked on the same link again
this.setState(state => ({
expandedTierOneName: state.expandedTierOneName?"":selectedLinkName
}))
} else {
this.setState(state=>({
expandedTierOneName:selectedLinkName
}))
}
this.setState(state => ({ selectedTierOneLink: selectedLinkName }));
this.setState(state => ({ selectedTierTwoLink: "" }));
} else if (selectedTier == "tier2"){
this.setState(state => ({ selectedTierTwoLink: selectedLinkName }));
this.setState(state => ({ selectedTierThreeLink: "" }));
} else {
this.setState(state => ({ selectedTierThreeLink: selectedLinkName }));
this.setState(state => ({ selectedTierTwoLink: "" }));
}
};
render(){
const { navConfig } = this.props;
return(
<nav className="mdc-drawer mdc-drawer--permanent mdc-typography my-nav-drawer">
<div className="my-nav-drawer__links">
<NavigationDrawerAppTiers
tierOneArray={navConfig.tierOneLinks}
onSelect={this.onSelect}
selectedTierOneLink={this.state.selectedTierOneLink}
selectedTierTwoLink={this.state.selectedTierTwoLink}
selectedTierThreeLink={this.state.selectedTierThreeLink}
expandedTierOneName={this.state.expandedTierOneName}
/>
</div>
</nav>
);
}
}
React.render(
<NavigationDrawer navConfig={navConfigData}/>,
document.body
);
The component is max upto three levels and I don't want it to achieve currently with recursion.
I am looking for a behavior to close the previously opened Parent tree node after selecting any child from another parent tree.
To explain in detail from the Codepen example, I need help on the below scenario.
Currently if no child Items are selected, only One parent node will be toggled.
I need help in achieving the below scenario.
Click on any child Item from First page,
Now clicking on the Third page should not collapse the First Page(since an item is selected from First Page), First Page should collapse when any child item from Third page is selected.
If no child Item is selected from Third page and Third Page is clicked again then all the opened parent nodes (First page and third page) should be collapsed.
Could someone help me with modifying the codpen.
Below code throws initialState is not defined
class NavigationDrawer extends React.Component {
static propTypes = {
navConfig: object.isRequired
};
initialState = this.props.navConfig.tierOneLinks.reduce((acc, { name }) => {
acc[name] = false;
return acc;
}, {});
state = {
expanded: initialState,
isDrawerCollapsed: false,
selectedTierOneLink: "",
selectedTierTwoLink: "",
selectedTierThreeLink: "",
expandedTierOneName: ""
};
onSelect = (selectedLinkName, selectedTier) => {
if(selectedTier == "tier1"){
if (selectedLinkName === this.state.selectedTierOneLink) {
// User clicked on the same link again
this.setState(state => ({
expandedTierOneName: state.expandedTierOneName?"":selectedLinkName
}))
} else {
this.setState(state=>({
expandedTierOneName:selectedLinkName
}))
}
this.setState(state => ({ selectedTierOneLink: selectedLinkName }));
this.setState(state => ({ selectedTierTwoLink: "" }));
} else if (selectedTier == "tier2"){
this.setState(state => ({ selectedTierTwoLink: selectedLinkName }));
this.setState(state => ({ selectedTierThreeLink: "" }));
} else {
this.setState(state => ({ selectedTierThreeLink: selectedLinkName }));
this.setState(state => ({ selectedTierTwoLink: "" }));
}
};
render(){
const { navConfig } = this.props;
return(
<nav className="mdc-drawer mdc-drawer--permanent mdc-typography my-nav-drawer">
<div className="my-nav-drawer__links">
<NavigationDrawerAppTiers
tierOneArray={navConfig.tierOneLinks}
onSelect={this.onSelect}
selectedTierOneLink={this.state.selectedTierOneLink}
selectedTierTwoLink={this.state.selectedTierTwoLink}
selectedTierThreeLink={this.state.selectedTierThreeLink}
expandedTierOneName={this.state.expandedTierOneName}
/>
</div>
</nav>
);
}
}
I'm not going to edit the Codepen and do all the work for you, but I'll give you a general idea of how to achieve your requirements.
Click on any child Item from First page, Now clicking on the Third page should not collapse the First Page
To accomplish this, you need a separate expanded state for each tierOneLinks:
state = {
expanded: {},
};
Then you can use state.expanded[tierOne.name] to check if the tier one panel is expanded, and expand it with
this.setState(prevState => ({
expanded: {
...prevState.expanded, // leave other panels expanded
[tierOne.name]: true,
},
}));
First Page should collapse when any child item from Third page is selected.
To accomplish this, the child item's onClick handler need to call a callback function (from the parent component, passed as props) that does the following:
this.setState({
expanded: {
[tierOne.name] : true, // collapse all other panels
}
})
If no child Item is selected from Third page and Third Page is clicked again then all the opened parent nodes (First page and third page) should be collapsed.
To accomplish this, check if the current page is already expanded, and if so, set all panels to collapsed:
if (state.expanded[tierOne.name]) this.setState({ expanded: {} });
Alternative
Use a Set or array as the state for the list of expanded pages.
You need to create a copy of the previous state when you want to update it.
Related
Adding a Modal Instead of "prompt" in react app
Hi Iam creating a menu builder with react-sortable-tree. I want to add a Modal when clicked on Add or Edit Task. prompt is working fine here but i want Modal to be opened. I have created state and finctions for modal but unable to render on UI when clicked. anybody help
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.css";
import "react-sortable-tree/style.css";
import { Button } from "react-bootstrap";
import "./MenuBuilder.css";
import { Modal } from "react-responsive-modal";
import "react-responsive-modal/styles.css";
import treeData from "./MenuBuilderData";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faTrashAlt, faPlus, faPen } from "#fortawesome/free-solid-svg-icons";
import SortableTree, {
toggleExpandedForAll,
getNodeAtPath,
addNodeUnderParent,
removeNode,
changeNodeAtPath,
} from "react-sortable-tree";
const maxDepth = 5;
export default class MenuBuilder extends Component {
constructor(props) {
super(props);
this.state = {
treeData: treeData,
searchString: "",
searchFocusIndex: 0,
searchFoundCount: null,
openModal: false,
};
}
onOpenModal = (e) => {
e.preventDefault();
this.setState({ openModal: true });
};
onCloseModal = () => {
this.setState({ openModal: false });
};
handleTreeOnChange = (treeData) => {
this.setState({ treeData });
};
selectPrevMatch = () => {
const { searchFocusIndex, searchFoundCount } = this.state;
this.setState({
searchFocusIndex:
searchFocusIndex !== null
? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
: searchFoundCount - 1,
});
};
selectNextMatch = () => {
const { searchFocusIndex, searchFoundCount } = this.state;
this.setState({
searchFocusIndex:
searchFocusIndex !== null
? (searchFocusIndex + 1) % searchFoundCount
: 0,
});
};
toggleNodeExpansion = (expanded) => {
this.setState((prevState) => ({
treeData: toggleExpandedForAll({
treeData: prevState.treeData,
expanded,
}),
}));
};
getNodeKey = ({ treeIndex: number }) => {
if (number === -1) {
number = null;
}
return number;
};
handleSave = () => {
console.log(JSON.stringify(this.state.treeData));
};
editTask = (path) => {
let editedNode = getNodeAtPath({
treeData: this.state.treeData,
path: path,
getNodeKey: ({ treeIndex }) => treeIndex,
ignoreCollapsed: true,
});
let newTaskTitle = prompt("Task new name:", editedNode.node.title);
if (newTaskTitle === null) return false;
editedNode.node.title = newTaskTitle;
let newTree = changeNodeAtPath({
treeData: this.state.treeData,
path: path,
newNode: editedNode.node,
getNodeKey: ({ treeIndex }) => treeIndex,
ignoreCollapsed: true,
});
// console.log(newTree);
this.setState({ treeData: newTree });
};
addTask = (path) => {
let parentNode = getNodeAtPath({
treeData: this.state.treeData,
path: path,
getNodeKey: ({ treeIndex }) => treeIndex,
ignoreCollapsed: true,
});
let newTaskTitle = parentNode.node.children ? prompt("Task name:", "default") && prompt("form ID:", "default") : prompt("Task name:", "default") // let newFormId = prompt("Form Id:", "");
if (newTaskTitle === null) return false;
let NEW_NODE = { title: newTaskTitle };
// let NEW_ID = { id: newFormId };
let parentKey = this.getNodeKey(parentNode);
let newTree = addNodeUnderParent({
treeData: this.state.treeData,
newNode: NEW_NODE,
// newId: NEW_ID,
expandParent: true,
parentKey: parentKey,
getNodeKey: ({ treeIndex }) => treeIndex,
});
this.setState({ treeData: newTree.treeData });
};
removeTask = (path) => {
let newTree = removeNode({
treeData: this.state.treeData,
path: path,
ignoreCollapsed: true,
getNodeKey: ({ treeIndex }) => treeIndex,
});
this.setState({ treeData: newTree.treeData });
};
renderTasks = () => {
const { treeData, searchString, searchFocusIndex } = this.state;
return (
<>
<SortableTree
treeData={treeData}
onChange={this.handleTreeOnChange}
maxDepth={maxDepth}
searchQuery={searchString}
searchFocusOffset={searchFocusIndex}
canDrag={({ node }) => !node.noDragging}
canDrop={({ nextParent }) => !nextParent || !nextParent.noChildren}
searchFinishCallback={(matches) =>
this.setState({
searchFoundCount: matches.length,
searchFocusIndex:
matches.length > 0 ? searchFocusIndex % matches.length : 0,
})
}
isVirtualized={true}
generateNodeProps={(taskInfo) => ({
buttons: [
<Button
variant="link"
onClick={() => this.editTask(taskInfo.path)}
>
<FontAwesomeIcon icon={faPen} color="#28a745" />
</Button>,
<Button
variant="link"
onClick={() => this.addTask(taskInfo.path)}
>
<FontAwesomeIcon icon={faPlus} color="#007bff" />
</Button>,
<Button
variant="link"
onClick={() => this.removeTask(taskInfo.path)}
>
<FontAwesomeIcon icon={faTrashAlt} color="#dc3545" />
</Button>,
],
})}
/>
<Button style={{ width: "100px" }} onClick={this.handleSave}>
save
</Button>
</>
);
};
render() {
return (
<>
<div className="wrapper">{this.renderTasks()}</div>
{/* <div>
<button onClick={this.onOpenModal}>Click Me</button>
<Modal open={this.state.openModal} onClose={this.onCloseModal}>
<input type="text" />
</Modal>
</div> */}
</>
);
}
}
treeData.js
const treeData = [
{
expanded: true,
title: "Contact HR",
children: [
{
expanded: true,
title: "Build relationships"
},
{
expanded: true,
title: "Take a test assignment"
}
]
},
{
expanded: true,
title: "Complete a test assignment",
children: [
{
expanded: true,
title: "Send link to this doc through LinkedIn"
}
]
},
{
expanded: true,
title: "Discuss Proposal details",
children: [
{
expanded: true,
title: "Prepare list of questions."
},
{
expanded: true,
title: "Other coming soon..."
}
]
},
{
expanded: true,
title: "Make an appointment for a technical interview",
children: [
{
expanded: true,
title: "Discuss details of the technical interview"
},
{
expanded: true,
title: "Prepare to Technival Interview"
}
]
},
{
expanded: true,
title: "Accept or Decline Offer"
}
];
export default treeData;
I am trying to create a button that will make visible a form to edit any contact on my list. However, when I press the button, nothing happens.
I have the initial state set to
this.state = {
contacts: [],
showEditWindow: false,
EditContactId: ''
};
I added a function:
editContact = (id) => {
this.setState({
showEditWindow: true, EditContactId: {id}
});
};
and a column:
{
title: "",
key: "action",
render: (record) => (
<button onClick={() => this.editContact(record.id)}
>
Edit
</button>
)
},
I imported EditContactModal and call it as
<EditContactModal reloadContacts={this.reloadContacts}
showEditWindow={this.state.showEditWindow}
EditContactId={this.state.EditContactId}/>
If I manually set this.state to showEditWindow:true, the window appears; however, either this.editContact(id) is not being called or it is not changing the state.
Calling this.deleteContact(id) works fine, as does setState in loadContacts() and reloadContacts()
What I am doing wrong?
Below are the full components.
Contacts.jsx
import { Table, message, Popconfirm } from "antd";
import React from "react";
import AddContactModal from "./AddContactModal";
import EditContactModal from "./EditContactModal";
class Contacts extends React.Component {
constructor(props) {
super(props);
this.state = {
contacts: [],
showEditWindow: false,
EditContactId: ''
};
this.editContact = this.editContact.bind(this);
};
columns = [
{
title: "First Name",
dataIndex: "firstname",
key: "firstname"
},
{
title: "Last Name",
dataIndex: "lastname",
key: "lastname"
},{
title: "Hebrew Name",
dataIndex: "hebrewname",
key: "hebrewname"
},{
title: "Kohen / Levi / Yisroel",
dataIndex: "kohenleviyisroel",
key: "kohenleviyisroel"
},{
title: "Frequent",
dataIndex: "frequent",
key: "frequent",
},{
title: "Do Not Bill",
dataIndex: "donotbill",
key: "donotbill"
},
{
title: "",
key: "action",
render: (record) => (
<button onClick={() => this.editContact(record.id)}
>
Edit
</button>
)
},
{
title: "",
key: "action",
render: (_text, record) => (
<Popconfirm
title="Are you sure you want to delete this contact?"
onConfirm={() => this.deleteContact(record.id)}
okText="Yes"
cancelText="No"
>
<a type="danger">
Delete{" "}
</a>
</Popconfirm>
),
},
];
componentDidMount = () => {
this.loadContacts();
}
loadContacts = () => {
const url = "http://localhost:3000/contacts";
fetch(url)
.then((data) => {
if (data.ok) {
return data.json();
}
throw new Error("Network error.");
})
.then((data) => {
data.forEach((contact) => {
const newEl = {
key: contact.id,
id: contact.id,
firstname: contact.firstname,
lastname: contact.lastname,
hebrewname: contact.hebrewname,
kohenleviyisroel: contact.kohenleviyisroel,
frequent: contact.frequent.toString(),
donotbill: contact.donotbill.toString(),
};
this.setState((prevState) => ({
contacts: [...prevState.contacts, newEl],
}));
});
})
.catch((err) => message.error("Error: " + err));
};
reloadContacts = () => {
this.setState({ contacts: [] });
this.loadContacts();
};
deleteContact = (id) => {
const url = `http://localhost:3000/contacts/${id}`;
fetch(url, {
method: "delete",
})
.then((data) => {
if (data.ok) {
this.reloadContacts();
return data.json();
}
throw new Error("Network error.");
})
.catch((err) => message.error("Error: " + err));
};
editContact = (id) => {
this.setState({
showEditWindow: true, EditContactId: {id}
});
};
render = () => {
return (
<>
<Table
className="table-striped-rows"
dataSource={this.state.contacts}
columns={this.columns}
pagination={{ pageSize: this.pageSize }}
/>
<AddContactModal reloadContacts={this.reloadContacts} />
<EditContactModal reloadContacts={this.reloadContacts}
showEditWindow={this.state.showEditWindow}
EditContactId={this.state.EditContactId}/>
</>
);
}
}
export default Contacts;
EditContactModal.jsx
import { Button, Form, Input, Modal, Select } from "antd";
import React from "react";
import ContactForm from './ContactForm';
const { Option } = Select;
class EditContactModal extends React.Component {
formRef = React.createRef();
state = {
visible: this.props.showEditWindow,
};
onFinish = (values) => {
const url = `http://localhost:3000/contacts/${this.props.EditContactId}`;
fetch(url, {
method: "put",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
})
.then((data) => {
if(data.ok) {
this.handleCancel();
return data.json();
}
throw new Error("Network error.");
})
.then(() => {
this.props.reloadContacts();
})
.catch((err) => console.error("Error: " + err))
};
showModal = () => {
this.setState({
visible: true,
});
};
handleCancel = () => {
this.setState({
visible: false,
});
};
render() {
return (
<>
{/*<Button type="primary" onClick={this.showModal}>
Create New +
</Button>*/}
<Modal
title="Edit Contact"
visible={this.state.visible}
onCancel={this.handleCancel}
footer={null}
>
<ContactForm />
</Modal>
</>
);
}
}
export default EditContactModal;
if your aim is to perform an update to the state object, you must not pass mutable data, but copy it instead into a new object.
this will allow the state changes to be picked up.
so, prefer setState({ ...state, ...someObject }) over setState(someObject).
I've added react-table package to my project and everything is fine, but I also wanted to have a possibility to right click on a row and perform some actions on it (cancel, pause etc). I'm using React with Typescript but I hope it doesn't add any complexity.
My initial idea was to use react-contextify, however I can't find any working examples that would combine react-table and react-contextify together.
The only "working" example I have found is this one:
React Context Menu on react table using react-contexify
I ended up not using react-contextify and it "kind of works" but I'm not totally certain about this one as I sometimes keep getting exceptions like this:
Uncaught TypeError: Cannot read property 'original' of undefined
The code I have now is this:
const columns = [
{
Header: "Name",
accessor: "name"
},
{
Header: "Age",
accessor: "age",
Cell: (props: { value: React.ReactNode }) => (
<span className="number">{props.value}</span>
)
},
{
id: "friendName", // Required because our accessor is not a string
Header: "Friend Name",
accessor: (d: { friend: { name: any } }) => d.friend.name // Custom value accessors!
},
{
Header: (props: any) => <span>Friend Age</span>, // Custom header components!
accessor: "friend.age"
}
];
return (
<div>
<ContextMenuTrigger id="menu_id">
<ReactTable
data={data}
columns={columns}
showPagination={false}
getTdProps={(
state: any,
rowInfo: any,
column: any,
instance: any
) => {
return {
onClick: (e: any, handleOriginal: any) => {
const activeItem = rowInfo.original;
console.log(activeItem);
},
onContextMenu: () => {
console.log("contextMenu", rowInfo);
this.setState({
showContextMenu: true,
rowClickedData: rowInfo.original
});
}
};
}}
/>
</ContextMenuTrigger>
{this.state.showContextMenu ? (
<MyAwesomeMenu clickedData={this.state.rowClickedData} />
) : null}
</div>
);
}
}
const MyAwesomeMenu = (props: { clickedData: any }) => (
<ContextMenu id="menu_id">
<MenuItem
data={props.clickedData}
onClick={(e, props) => onClick({ e, props })}
>
<div className="green">ContextMenu Item 1 - {props.clickedData.id}</div>
</MenuItem>
</ContextMenu>
);
const onClick = (props: {
e:
| React.TouchEvent<HTMLDivElement>
| React.MouseEvent<HTMLDivElement, MouseEvent>;
props: Object;
}) => console.log("-------------->", props);
What is the best (and simplest) way to add a context menu to react-table so I can use clicked row's props? I really like react-contextify but haven't found any examples.
Thanks
React Hooks exmaple on dev.to
Class Based Compnent example on codepen
class App extends React.Component {
constructor() {
super();
this.state = {
value: ''
};
}
render() {
return(
<div>
{
['row1', 'row2', 'row3'].map((row) => {
return (
<ContextMenu
key={row}
buttons={[
{ label: 'Editovat', onClick: (e) => alert(`Editace ${row}`) },
{ label: 'Smazat', onClick: (e) => alert(`Mažu ${row}`) }
]}
>
<div className="row">{row}</div>
</ContextMenu>
);
})
}
</div>
);
}
}
class ContextMenu extends React.Component {
static defaultProps = {
buttons: []
};
constructor() {
super();
this.state = {
open: false
};
}
componentDidMount() {
document.addEventListener('click', this.handleClickOutside);
document.addEventListener('contextmenu', this.handleRightClickOutside);
}
handleClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const context = ReactDOM.findDOMNode(this.context);
const isInRow = (!root.contains(e.target) || root.contains(e.target));
const isInContext = !context.contains(e.target);
if (isInRow && isInContext) {
this.setState({
open: false
});
}
}
handleRightClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const isInRow = !root.contains(e.target);
if (isInRow) {
this.setState({
open: false
});
}
}
handleRightClick = (e) => {
e.preventDefault();
console.log(e.nativeEvent, window.scrollY);
this.setState({
open: true,
top: window.scrollY + e.nativeEvent.clientY,
left: e.nativeEvent.clientX,
});
}
render() {
return (
<div
onContextMenu={this.handleRightClick}
ref={(node) => this.div = node}
>
{this.props.children}
{
!this.state.open
? null
: <div
className="context"
ref={(div) => this.context = div}
style={{ top: this.state.top, left: this.state.left }}
>
<ul>
{
// button - name, onClick, label
this.props.buttons.length > 0 &&
this.props.buttons.map((button) => {
return <li key={button.label}>
<a href="#" onClick={button.onClick}>
{button.label}
</a>
</li>
})
}
</ul>
</div>
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Here I'm fetching data with ApiClient and after that I make operation to confirm to delete the current item. My question here is how i can reload the page after I click Yes ?
type Props = {
resourceName: string,
label: string,
accessor: string,
row: Object,
}
#withRouter
#connectAPI((apiClient: ApiClient, props: Props) => ({
deleteObject: {
send: (id: any) => apiClient.resources[props.resourceName].delete(id),
success: () => {
// TODO: ?
},
},
}))
class DeleteComponent extends React.Component<Props> {
state = {
open: false,
};
handleOpen = () => {
this.setState({ open: true });
};
handleConfirm = (props: Props) => {
const { accessor, row, deleteObject } = props;
const id = row._original[accessor];
deleteObject({
id,
});
};
render(): React.ReactNode {
const { open } = this.state;
let message = null;
if (open) {
message = (
<MessageDialog
message="Are tou sure you want to delete ?"
actions={{
yes: {
label: 'Yes',
callback: this.handleConfirm,
},
no: {
label: 'No',
callback: (e: SyntheticMouseEvent<HTMLButtonElement>) => this.setState({ open: false }),
},
}}
/>
);
}
return (
<React.Fragment>
<Button
position="right"
onClick={this.handleOpen}
hoverIndicator="light-1"
/>
{message}
</React.Fragment>
);
}
}
export default DeleteComponent;
I am having 4 buttons each button have name id and selected boolean flag.
What I am trying to achieve is, on click of button, boolean button flag should be changed of that particular button. For this, I need to setState in map function for that particular button Id.
My issue is I am unable to setState in map function for that particular clicked button, its btnSelected should be changed
My aim is to create a multi-select deselect button.Its kind of interest selection for the user and based on that reflect the UI as well my array. Here is my code.
Thanks in anticipation.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class Test extends Component {
constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: "",
numbers: [1, 2, 3, 4, 5],
posts: [
{
id: 1,
topic: "Animal",
btnSelected: false
},
{
id: 2,
topic: "Food",
btnSelected: false
},
{
id: 3,
topic: "Planet",
btnSelected: false
},
{ id: 4, topic: "Nature", btnSelected: false }
],
allInterest: []
};
}
handleChange(e) {
//console.log(e.target.value);
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value });
}
getInterest(id) {
this.state.posts.map(post => {
if (id === post.id) {
//How to setState of post only btnSelected should change
}
});
console.log(this.state.allInterest);
if (this.state.allInterest.length > 0) {
console.log("Yes we exits");
} else {
console.log(id);
this.setState(
{
allInterest: this.state.allInterest.concat(id)
},
function() {
console.log(this.state);
}
);
}
}
render() {
return (
<div>
{this.state.posts.map((posts, index) => (
<li
key={"tab" + index}
class="btn btn-default"
onClick={() => this.getInterest(posts.id)}
>
{posts.topic}
<Glyphicon
glyph={posts.btnSelected === true ? "ok-sign" : "remove-circle"}
/>
</li>
))}
</div>
);
}
}
Here's how you do something like this:
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (e) => {
const { posts } = this.state;
const { id } = e.target;
posts[id].selected = !this.state.posts[id].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="radio" id={i} key={i} checked={p.selected} onClick={this.handleClick} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here.
You can do this by passing the index from the map into each button's handleClick function, which would then return another function that can be triggered by an onClick event.
In contrast to Colin Ricardo's answer, this approach avoids adding an id prop onto each child of the map function that is only used for determining the index in the handleClick. I've modified Colin's example here to show the comparison. Notice the event parameter is no longer necessary.
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (index) => () => {
const { posts } = this.state;
posts[index].selected = !this.state.posts[index].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="checkbox" key={i} checked={p.selected} onClick={this.handleClick(i)} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here