Primereact tree context menu not hiding after an action - reactjs

I'm using Primereact's tree along with context menu. After performing an action (a click event on one of the menu items), context menu is not hiding. If I click anywhere on the screen the context menu hides. Here is the code. I'm following the same pattern provided by primereact. Can you help me figure out how to fix this?
DemoPrimeReact.js
import React, {useContext, useEffect, useRef, useState} from 'react';
import 'primereact/resources/themes/saga-blue/theme.css';
import 'primereact/resources/primereact.css';
import 'primeflex/primeflex.css';
import {Tree} from 'primereact/tree';
// other imports
const DemoPrimeReact = () => {
// .... other lines skipped
const [selectedNodeKey, setSelectedNodeKey] = useState(null);
const contextMenu = useRef(null);
return (
<>
<DemoContextMenu
contextMenu={contextMenu} setSelectedNodeKey={setSelectedNodeKey}
//... other props
/>
<Tree
value={[withOrWithoutSearchNodes[0]]}
selectionMode="single" selectionKeys={selectedKey} onSelectionChange={handleSelectionChange}
onSelect={onNodeSelect} onUnselect={onNodeUnselect}
expandedKeys={expandedKeys} onToggle={(event) => setExpandedKeys(event.value)}
contextMenuSelectionKey={selectedNodeKey}
onContextMenuSelectionChange={(event) => setSelectedNodeKey(event.value)}
onContextMenu={(event) => contextMenu.current.show(event.originalEvent)}
dragdropScope="catalog-dnd" onDragDrop={(event) => {
handleCatalogDragAndDrop(event, setLoading, setCanPaste, loadChildrenOnExpand,
setSelectedKey, setExpandedKeys, onNodeSelect, setNodes, expandedKeys);
}}
onExpand={loadChildrenOnExpand} className={classes.primeTree} nodeTemplate={nodeTemplate}
/>
</>
);
}
export default DemoPrimeReact;
DemoContextMenu.js
import React, {useState} from 'react';
import {ContextMenu} from "primereact/contextmenu";
const DemoContextMenu = (props) => {
const { contextMenu, setSelectedNodeKey } = props;
const menu = [
{
label: "Create Library",
template: (item, options) => {
return (
<>
{
nodeInfo && (nodeInfo.node.value.entryType === 'M' ||
nodeInfo.node.value.entryType === 'L') ?
<span
style={{padding: '0 0 0 15px'}}
className={options.className}
>
<Button
onClick={(event) => handleCatalogAction(event, 'createLibrary')}
className={classes.button}
>
<CreateLibraryIcon style={{color: '#68de86'}} />
<span className={classes.item}>Create Library</span>
</Button>
</span> : null
}
</>
);
},
}
];
return (
<>
<ContextMenu
model={menu}
ref={contextMenu}
onHide={() => setSelectedNodeKey(null)}
className={classes.contextMenu}
/>
</>
);
}
export default DemoContextMenu

I think you can hide the context menu with contextMenu ref using contextMenu.current.state.visible = false;.
You can put this line in handleCatalogAction(...) function at starting. Before you add the above line, you can do console.log(contextMenu); to check for current.state.visible value.

Related

I need to pass some data through some components React

I'm doing a broker simulator and I need to pass the data (side,price,volume,timestamp) into different tab, which is like grandparent's brother. The problem is I have no idea, how to transport data that far
Modalbuy.js
import React from "react";
import "./modal.css";
const Modalbuy = ({active, setActive,price}) => {
return (
<div className={active ? "modal active" : "modal"} onClick={() => setActive(false)}>
<div className="modal__content" onClick={e => e.stopPropagation()}>
<header>Make order</header>
<p>BUY {price}</p>
<input placeholder="Volume">{volume}</input>
<div>
<button onClick={() =>{this.props.addData("BUY", price, volume,)}}>Ok</button>
<button>Cancel</button>
</div>
</div>
</div>
)
}
export default Modalbuy;
Select.js
import React, {useState} from "react";
import './select.css'
import Modalbuy from "../popup/Modalbuy"
import Modalsell from "../popup/Modalsell"
import { useEffect } from "react";
const Select = () => {
const [value, setValue] = useState("");
const [usdrub, setUsdrub] = useState(Math.random() * (64-61) + 61);
useEffect(() => {
const interval = setInterval(() => {
setUsdrub(Math.random() * (64-61) + 61);
}, 10000);
return () => clearInterval(interval);
}, [])
const [rubusd, setRubusd] = useState(Math.random() * 2);
useEffect(() => {
const interval = setInterval(() => {
setRubusd(Math.random() * 2);
}, 10000);
return () => clearInterval(interval);
}, [])
function changeSelect(event) {
setValue(event.target.value)
}
const [modalBuyActive, setModalBuyActive] = useState(false)
const [modalSellActive, setModalSellActive] = useState(false)
function addData(side, price, volume, timestamp) {
this.setState({side:side, price:price, volume:volume, timestamp:timestamp})
}
return (
<div>
<select value = {value} onChange={changeSelect}>
<option>Choose instrument</option>
<option name="USD/RUB" value={usdrub}>USD/RUB</option>
<option name="RUB/USD" value={rubusd}>RUB/USD</option>
</select>
<div className="Curr">
<div className="Buy" name="buy"> <button className="Buy" type="btn" onClick={() => setModalBuyActive(true)}>BUY {value + 1} </button>
</div>
<div className="Sell" name="sell"><button className="Sell" type="btn" onClick={() => setModalSellActive(true)}>SELL {value}</button></div>
</div>
<Modalbuy active={modalBuyActive} setActive={setModalBuyActive} price={value + 1} addData={addData}/>
<Modalsell active={modalSellActive} setActive={setModalSellActive} price={value}/>
</div>
)
}
export default Select
Trading.js
import React from "react";
import Timer from "./Timer";
import Select from "./select_curr/Select";
const Trading = () => {
return (
<div>
<Timer/>
<Select/>
</div>
)
}
export default Trading;
Page.js
import React from 'react'
import { Tabs, TabList, TabPanel, Tab } from 'react-re-super-tabs'
import CustomTab from './customTab'
import Trading from '../trading/Trading.js'
import Table from '../archive/table'
const Page = () => {
return (
<div>
<Tabs activeTab='about'>
<TabList>
<Tab component={CustomTab} label='Trading' id='trading' />
<Tab component={CustomTab} label='Archive' id='archive' />
</TabList>
<TabList>
<TabPanel component={Trading} id='trading' />
<TabPanel component={Table} id='table' />
</TabList>
</Tabs>
</div>
)
}
export default Page;
So I need to make a table in Table.js and import there data I got from module dialogue
To summarize the way I understand your problem, The component Modalbuy need to share data with your component Table.
Here are some way I can advise you.
1. The common Parent manage the state
const Page = () => {
const [yourData, setYourData] = useState([])
return (
[...]
<TabPanel component={props=>(<Trading {...props} value={yourData} onChange={setYourData} />)} id='trading' />
<TabPanel component={props=>(<Table {...props} value={yourData} onChange={setYourData} />)} id='table' />
[...]
)
}
This implies intermediate component are in charge to follow those new props to Modalbuy
2. Using Redux librairy
Redux is basicly a librairy to help you to define a global state for your app. You define actions your components can call to transform this global state. On the other way, components can subscribe to that state to be refreshed when a change happen.
Don't hesitate to have a look to tutorials https://react-redux.js.org/tutorials/quick-start
3. Using React Context
export const YourDataContext = React.createContext({})
const Page = () => {
const [yourData, setYourData] = useState([])
return (
<YourDataContext.Provider value={{yourData, setYourData}}>
[...]
</YourDataContext.Provider>
)
}
const Modalbuy = ({active, setActive,price}) => {
const { yourData, setYourData } = React.useContext(YourDataContext)
[...]
})
You define a context YourDataContext. You share a value to this context, here we create a state and passing the getter/setter. All child of the Provider can access the attribute value with the hook React.useContext(YourDataContext)

React 18: Button click in map function to reflect information only related to one item and not all items

I am new to React and using React 18 in this app. My problem is that if I click one button inside a map function, it reflects information about all the items. I want only that item information to show for which I clicked the button. The isShown === true part in the CountryInfo.js file is what should reflect only one item; currently clicking the show button shows all item information on the UI (I don't want this to happen). How do I do this?
Visually, this is my UI,
If you see the image above, clicking any show button returns all countries information, which should not happen.
Below is my code:
App.js
import { useState, useEffect } from 'react';
import axios from "axios";
import CountryInfo from './components/CountryInfo';
const App = () => {
const [countries, setCountries] = useState([]);
const [searchCountry, setSearchCountry] = useState("");
const handleCountryChange = event => {
setSearchCountry(event.target.value);
}
const getAllCountriesData = () => {
axios.get("https://restcountries.com/v3.1/all")
.then(response => {
setCountries(response.data);
})
}
useEffect(() => {
getAllCountriesData();
}, []);
return (
<>
<h2>Data for countries</h2>
find countries:
<input value={searchCountry} onChange={handleCountryChange} />
{searchCountry.length > 0 && <CountryInfo countries={countries} searchCountry={searchCountry} />}
</>
)
}
export default App;
CountryInfo.js
import React from "react";
import { useState } from "react";
const CountryInfo = ({ countries, searchCountry }) => {
const [isShown, setIsShown] = useState(false);
let filteredList = countries.filter(country =>
country.name.common.toLowerCase().includes(searchCountry.toLowerCase()));
const handleClick = () => {
setIsShown(true);
}
if (filteredList.length > 10) {
return <div>Too many matches, specify another filter</div>
}
else {
return filteredList.map(country => {
return (
<>
<div key={country.name.common}>
{!isShown &&
<div>
{country.name.common}
<button type="submit" onClick={handleClick}>show</button>
</div>
}
{isShown &&
<div key={country.name.common}>
<h2>{country.name.common}</h2>
<p>
Capital: {country.capital}
{'\n'}
Area: {country.area}
</p>
Languages:
<ul>
{
Object.values(country.languages)
.map((language, index) => <li key={index}>{language}</li>)
}
</ul>
<img src={country.flags.png} alt={`${country.name.common} flag`} height={150} />
</div>
}
</div>
</>
)
})
}
}
export default CountryInfo;

Add active class by default to the first element from class list and change active class on click react

I'm a newbie in react. I have two class in css file. One is btn and another is btn-active. I want to set an btn-active class to the first button by default and when I click on other buttons it'll be remove and add to the current button. I'll be thankful if anyone help me about this. Thanks in advance.
import { useState } from "react";
import buttons from "../data/buttons.json";
const Home = () => {
return (
<div>
{buttons.map((item, index) => {
return (
<button key={index} className="btn">
{item.valu}
</button>
);
})}
</div>
);
};
Keep a state to handle the selected button index.
Try like below:
import { useState } from "react";
const Home = () => {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div>
{buttons.map((item, index) => {
return (
<button
key={index}
onClick={() => setSelectedIndex(index)}
className={`btn ${selectedIndex === index ? "active" : ""}`}
>
{item.valu}
</button>
);
})}
</div>
);
};

Opening links in new tab in React Native application

I am quite new to React and I need to make a dashboard, where I will have a table with some data.
One of the column in the table is clickable and when I click on a particular cell, it will take that cell value and display more details about that item "in a new tab".
Now this is easy with a browser, where you open a new tab on link click. But this is an app i am making on chromium. more of a desktop app.
But I do not want a new window whole together. I need a new tab panel to open with the previous table still there in one tab, and the details in the new tab.
so when I go back to the previous tab and click on another item, it opens a third tab with the details of this item.
Example below (Sorry I am not allowed to insert pictures yet. Please click the link to see them.)
1st picture with initial table.
First Picture With the initial table
Now, if I click on Accountant, a new tab should appear as in second image:
Second image with a new tab opened
I think I found a solution to this. I am pasting it here for someone looking for a solution.
import React, { useState, useCallback, useEffect } from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import { Tabs, Tab, IconButton } from "#material-ui/core";
import CloseIcon from "#material-ui/icons/Close";
import TabContainer from "../TabContainer/index.jsx";
const styles = theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper
},
colorPrimary: {
color: "red"
}
});
export const TabsDemo = ({
tabs,
selectedTab = 1,
onClose,
tabsProps = { indicatorColor: "primary" },
...rest
}) => {
const [activeTab, setActiveTab] = useState(selectedTab);
const [activetabs, setActiveTabs] = useState([]);
// useEffect(() => {
// if (activeTab !== selectedTab) setActiveTab(selectedTab);
// }, [setActiveTab, selectedTab, activeTab]);
// const handleChange = useCallback(event => setActiveTab(event.target.value), [
// setActiveTab,
// ]);
useEffect(() => {
setActiveTabs(tabs);
}, [tabs]);
const handleChange = useCallback((event, activeTab) => {
setActiveTab(activeTab);
}, []);
const handleClose = useCallback(
(event, tabToDelete) => {
event.stopPropagation();
const tabToDeleteIndex = activetabs.findIndex(
tab => tab.id === tabToDelete.id
);
const updatedTabs = activetabs.filter((tab, index) => {
return index !== tabToDeleteIndex;
});
const previousTab =
activetabs[tabToDeleteIndex - 1] ||
activetabs[tabToDeleteIndex + 1] ||
{};
setActiveTabs(updatedTabs);
setActiveTab(previousTab.id);
},
[activetabs]
);
return (
<>
<div>
<Tabs value={activeTab} onChange={handleChange}>
{activetabs.map(tab => (
<Tab
key={tab.id}
value={tab.id}
label={
typeof tab.label === "string" ? (
<span>
{" "}
tab.label
{tab.closeable && (
<IconButton
component="div"
onClick={event => handleClose(event, tab)}
>
<CloseIcon />
</IconButton>
)}
</span>
) : (
tab.label
)
}
/>
))}
</Tabs>
{activetabs.map(tab =>
activeTab === tab.id ? (
<TabContainer key={tab.id}>{tab.component}</TabContainer>
) : null
)}
</div>
</>
);
};
TabsDemo.propTypes = {
classes: PropTypes.object.isRequired,
tabs: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
id: PropTypes.number.isRequired,
component: PropTypes.object.isRequired,
closeable: PropTypes.bool.isRequired
}).isRequired
).isRequired
};
export default withStyles(styles)(TabsDemo);
Below is the link of a code sandbox that you can use to understand this fully :
https://codesandbox.io/s/mui-closeable-tab-gw2hw?file=/src/components/tabsdemo/index.jsx:0-3123

Importing custom toolbar component doesn't fire methods in react-big-calendar

When I create a custom toolbar in the same file as the Calendar I can use the onView('day') method totally fine. It changes the views. However, when I put the same CalendarToolbar in a different file, and import it in the Calendar file it, doesn't update or change the view. I get the methods as props but it doesn't change anything.
// CustomToolbar
const CalendarToolbar = ({ onView, label, views }) => (
<div>
<h2>
{label}
</h2>
{views.map(view => (
<button
key={view}
type="button"
onClick={() => onView(view)}
>
{view}
</button>
))}
</div>
);
<Calendar
localizer={localizer}
defaultDate={new Date()}
defaultView="day"
events={mockEvents}
style={{ height: '100vh' }}
onSelectEvent={this.handleSelectEvent}
components={{
toolbar: CalendarToolbar,
}}
/>
Just wondered what I'm doing wrong?
I recently wrote my own custom Toolbar component. I copied the original Toolbar, from the repository, and then replaced the render() method with my own, copying what they had done and including my own stuff. My implementation stuff isn't completely important, but if you look at the onClick bits below, it may help you in doing what you want to do:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import ToolbarDateHeader from './ToolbarDateHeader.component';
import { Icon, Button, ButtonGroup, ButtonToolbar } from '../app';
const navigate = {
PREVIOUS: 'PREV',
NEXT: 'NEXT',
TODAY: 'TODAY',
DATE: 'DATE'
};
const propTypes = {
view: PropTypes.string.isRequired,
views: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.node.isRequired,
localizer: PropTypes.object,
onNavigate: PropTypes.func.isRequired,
onView: PropTypes.func.isRequired
};
export default class Toolbar extends Component {
static propTypes = propTypes;
render() {
let {
localizer: { messages },
label,
date
} = this.props;
return (
<ButtonToolbar>
<ButtonGroup>
<Button onClick={this.navigate.bind(null, navigate.TODAY)}>
{messages.today}
</Button>
<Button onClick={this.navigate.bind(null, navigate.PREVIOUS)}>
<Icon glyph="caret-left" />
</Button>
<Button onClick={this.navigate.bind(null, navigate.NEXT)}>
<Icon glyph="caret-right" />
</Button>
</ButtonGroup>
<ToolbarDateHeader date={date} onChange={this.toThisDay}>
{label}
</ToolbarDateHeader>
<ButtonGroup className="pull-right">
{this.viewNamesGroup(messages)}
</ButtonGroup>
</ButtonToolbar>
);
}
toThisDay = date => {
this.props.onView('day');
// give it just a tick to 'set' the view, prior to navigating to the proper date
setTimeout(() => {
this.props.onNavigate(navigate.DATE, date);
}, 100);
};
navigate = action => {
this.props.onNavigate(action);
};
view = view => {
this.props.onView(view);
};
viewNamesGroup(messages) {
let viewNames = this.props.views;
const view = this.props.view;
if (viewNames.length > 1) {
return viewNames.map(name => (
<Button
key={name}
className={cn({
active: view === name,
'btn-primary': view === name
})}
onClick={this.view.bind(null, name)}
>
{messages[name]}
</Button>
));
}
}
}
import React, { useState, useEffect } from "react";
import clsx from "clsx";
import moment from "moment";
function RBCToolbar(props) {
const { label, date, view, views, onView, onNavigate } = props;
const [month, setMonth] = useState("January");
const mMonth = moment(date).format("MMMM");
const months = moment.months();
useEffect(() => {
setMonth(mMonth);
}, [mMonth]);
const onChange = (event) => {
const current = event.target.value;
onNavigate("DATE", moment().month(current).toDate());
setMonth(current);
};
const goToView = (view) => {
onView(view);
};
const goToBack = () => {
onNavigate("PREV");
};
const goToNext = () => {
onNavigate("NEXT");
};
const goToToday = () => {
onNavigate("TODAY");
};
return (
<div className="rbc-toolbar">
<div className="rbc-btn-group">
<button onClick={goToToday}>Today</button>
<button onClick={goToBack}>Back</button>
<button onClick={goToNext}>Next</button>
</div>
<div className="rbc-toolbar-label">
{view === "month" ? (
<>
<select className="rbc-dropdown" value={month} onChange={onChange}>
{months?.map((month) => (
<option
value={month}
className="rbc-dropdown-option" //custom class
key={month}
>
{month}
</option>
))}
</select>
<span className="rbc-year"> {moment(date).format("YYYY")}</span>
</>
) : (
label
)}
</div>
<div className="rbc-btn-group">
{views?.map((item) => (
<button
onClick={() => goToView(item)}
key={item}
className={clsx({ "rbc-active": view === item })}
>
{item}
</button>
))}
</div>
</div>
);
}
export default RBCToolbar;

Resources