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;
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'm using Zustand to store state, everything is working fine apart from this. When i click on the Song Buttons i want that to filter from the list.
Currently on fresh load it displays 3 songs. When clicking the button it should filter (and it does for first instance) but as soon as i click another button to filter again then nothing happens.
So if i chose / click Song 1 and Song 2 it should only show these songs.
I think the logic i wrote for that is correct but i must be doing something wrong with re-rendering.
Sorry i know people like to upload example here but i always find it hard with React files, so for this case I'm using https://codesandbox.io/s/damp-waterfall-e63mn?file=/src/App.js
Full code:
import { useEffect, useState } from 'react'
import create from 'zustand'
import { albums } from './albums'
export default function Home() {
const {
getFetchedData,
setFetchedData,
getAttrData,
setAttrData,
getAlbumData,
getButtonFilter,
setButtonFilter,
setAlbumData,
testState,
} = stateFetchData()
useEffect(() => {
if (getFetchedData) setAttrData(getFetchedData.feed.entry)
}, [getFetchedData, setAttrData])
useEffect(() => {
setAlbumData(getButtonFilter)
}, [getButtonFilter, setAlbumData])
// useEffect(() => {
// console.log('testState', testState)
// console.log('getAlbumData', getAlbumData)
// }, [getAlbumData, testState])
useEffect(() => {
setFetchedData()
}, [setFetchedData])
return (
<div>
<div>Filter to Show: {JSON.stringify(getButtonFilter)}</div>
<div>
{getAttrData.map((props, idx) => {
return (
<FilterButton
key={idx}
attr={props}
getDataProp={getButtonFilter}
setDataProp={setButtonFilter}
/>
)
})}
</div>
<div>
{getAlbumData?.feed?.entry?.map((props, idx) => {
return (
<div key={idx}>
<h1>{props.title.label}</h1>
</div>
)
})}
</div>
</div>
)
}
const FilterButton = ({ attr, getDataProp, setDataProp }) => {
const [filter, setFilter] = useState(false)
const filterAlbums = async (e) => {
const currentTarget = e.currentTarget.innerHTML
setFilter(!filter)
if (!filter) setDataProp([...getDataProp, currentTarget])
else setDataProp(getDataProp.filter((str) => str !== currentTarget))
}
return <button onClick={filterAlbums}>{attr.album}</button>
}
const stateFetchData = create((set) => ({
getFetchedData: albums,
setFetchedData: async () => {
set((state) => ({ ...state, getAlbumData: state.getFetchedData }))
},
getAttrData: [],
setAttrData: (data) => {
const tempArr = []
for (const iterator of data) {
tempArr.push({ album: iterator.category.attributes.label, status: false })
}
set((state) => ({ ...state, getAttrData: tempArr }))
},
getButtonFilter: [],
setButtonFilter: (data) => set((state) => ({ ...state, getButtonFilter: data })),
testState: {
feed: { entry: [] },
},
getAlbumData: [],
setAlbumData: (data) => {
set((state) => {
console.log('🚀 ~ file: index.js ~ line 107 ~ state', state)
const filter = state.getAlbumData.feed?.entry.filter((item) =>
data.includes(item.category.attributes.label),
)
return {
...state,
getAlbumData: {
...state.getAlbumData,
feed: {
...state.getAlbumData.feed,
entry: filter,
},
},
}
})
},
}))
Sample data:
export const albums = {
feed: {
entry: [
{ title: { label: 'Song 1' }, category: { attributes: { label: 'Song 1' } } },
{ title: { label: 'Song 2' }, category: { attributes: { label: 'Song 2' } } },
{ title: { label: 'Song 3' }, category: { attributes: { label: 'Song 3' } } },
],
},
}
In the following code, I have a Dropdown and a Button. Once you select an option in the Dropdown and click the Button, that value will be sent to Excel in the active cell. After sending this data to Excel, can I set the Dropdown back to the placeholder? Or can I set the Dropdown to the first value (which in this case is blank)? How can I set the Dropdown value back to placeholder or blank?
import * as React from "react";
import { Dropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { PrimaryButton } from 'office-ui-fabric-react/lib/';
export interface ParentProps {
};
export interface ParentState {
selectedItem?: { key: string | number | undefined };
operationType?;
};
export default class ParentComponent extends React.Component<ParentProps, ParentState> {
constructor(props, context) {
super(props, context);
this.state = {
operationType: '',
};
}
addToExcel = async () => {
try {
await Excel.run(async context => {
const range = context.workbook.getSelectedRange();
range.load("address");
await context.sync();
range.values = (this.state.operationType);
});
} catch (error) {
console.error(error);
}
this.setState({
})
};
render() {
const { selectedItem } = this.state;
const options: IDropdownOption[] = [
{ key: 'blank', text: '' },
{ key: 'topLevelMake', text: 'Parents', itemType: DropdownMenuItemType.Header },
{ key: 'topLevel', text: 'TOP LEVEL' },
{ key: 'make', text: 'MAKE ITEM' },
];
return (
<div>
<Dropdown
label="Operation"
selectedKey={selectedItem ? selectedItem.key : undefined}
onChange={this._onChange}
placeholder={"Select an option"}
options={options}
styles={{ dropdown: { width: 300 } }}
/>
<p></p>
<PrimaryButton
text="Enter"
onClick={this.addToExcel}
/>
</div>
);
}
private _onChange = (e, item: IDropdownOption): void => {
this.setState({ selectedItem: item });
this.setState({ operationType: item.text })
console.log(e);
}
};
Try something like this on your addToExcel():
addToExcel = async () => {
try {
await Excel.run(async context => {
const range = context.workbook.getSelectedRange();
range.load("address");
await context.sync();
range.values = (this.state.operationType);
});
} catch (error) {
console.error(error);
}
this.setState({
selectedItem: {key:'blank'},
})
};
You should update your state after your excel operations.
addToExcel = async () => {
try {
await Excel.run(async context => {
const range = context.workbook.getSelectedRange();
range.load("address");
await context.sync();
range.values = (this.state.operationType);
});
// update state after asyn operations is done
this.setState({
selectedItem:undefined
})
} catch (error) {
console.error(error);
}
};
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"));