How to trigger onClick of focused element by pressing enter in navbar - javascript

There is a list of nav items and its focus nav item below each press TAB key,
I need to fire the onClick event of the focused nav item when pressing the ENTER key.
This is what I do,
onKeyPress works as expected but do not trigger onClick
NavBar compoenent:
const Navbar = () => {
...
return (
<nav>
<ul style={{ listStyleType: 'none', padding: 0, margin: 0 }}>
{authenticatedRoutes.map((currentItem: any, index: number) => {
const isOpen = openedDrawer === currentItem.path
return (
<ul
key={`ROUTE_LIST_${index}`}
style={{
listStyleType: 'none',
}}
>
<li key={`ROUTE_${index}`}>
<div
onKeyPress={e => {
if (e.key === 'Enter') {
const event = new MouseEvent('click')
e.target.dispatchEvent(event)
}
}}
onClick={() => {
history.push(currentItem.path)
}}
role="link"
tabIndex={0}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
...LEFT_BORDER,
backgroundColor: isOpen ? 'transparent' : 'initial',
}}
/>
{linkText}
</div>
</div>
</li>
</ul>
)
})}
</ul>
</nav>
)

You can't programatically trigger native event hooks on a component easily in react.
The best solution would be to put the code in your onKeyPress as well...
onKeyPress={e => {
if (e.key === 'Enter') {
history.push(currentItem.path) // I Added this line
const event = new MouseEvent('click')
e.target.dispatchEvent(event)
}
}}
onClick={() => {
history.push(currentItem.path)
}}

Related

Update scroll so that cursor remains with the dragged item when new items are added

I have a list of items in a scrollable div (overflow-y: scroll). When I start dragging (in the real website I am using react-beautiful-dnd) some of the items will expand to show subitems since they are dropdown. This causes the position of the items to shift down and so the item that I was dragging moves downwards but my cursor remains in the same position.
Here's the link to the problem: https://codesandbox.io/s/gracious-einstein-vvsbtp?file=/src/App.js
import { useState } from "react";
export default function App() {
const [show, setShow] = useState(false);
const handleDrag = () => {
setShow(true);
};
const DisplayHello = () => {
return (
<>
{new Array(5).fill(0, 0, 5).map((ele, index) => {
return (
<p style={{ margin: "5px" }} key={index}>
Hello
</p>
);
})}
</>
);
};
return (
<div className="App" style={{ height: "400px" }}>
<div
style={{
display: "flex",
flexDirection: "column",
height: "100%",
border: "1px solid red",
width: "200px",
overflow: "scroll"
}}
>
<DisplayHello />
{show && <DisplayHello />}
<div
style={{ backgroundColor: "dodgerblue" }}
draggable="true"
onDrag={handleDrag}
>
Drag Me
</div>
{show && <DisplayHello />}
<DisplayHello />
</div>
</div>
);
}
What I want is that even if the items expand, the cursor should remain on the draggable item. Is this even possible?

Select only a card at a time on click in reactjs

I have a react component which has some cards, When I click the plus icon in the card, it would expand and show some data for 30sec and then the data will disappear and on click it will reappear again, here is the component
import React from "react";
import { FaPlus } from "react-icons/fa";
import useTimeout from "../../../common/useTimeout";
import { Modal } from "../../../common/Modal";
import { ToggleState } from "../../../common/Toggle";
import { BsSearch } from "react-icons/bs";
import AdvancedFilter from "./AdvancedFilter";
export default function CitiesList({ cities }: { cities: any }): JSX.Element {
const [filter, setFilter] = React.useState("");
const [sortType, setSortType] = React.useState("");
const [selectedCity, setSelectedCity] = React.useState<any | null>(null);
console.log(filter);
const sorted = cities.sort((a: { name: string }, b: { name: any }) => {
const isReversed = sortType === "asc" ? 1 : -1;
return isReversed * a.name.localeCompare(b.name);
});
const onSort = (sortType: React.SetStateAction<string>) => {
console.log("changed");
setSortType(sortType);
};
const [showMeta, setShowMeta] = React.useState(false);
const handleClick = () => setShowMeta(true);
const getSelectedCity = (selectedCity: any) => {
setSelectedCity(selectedCity);
console.log("SELECTED CITY", selectedCity);
};
const [visible, setVisible] = React.useState(true);
const hide = () => setVisible(false);
useTimeout(hide, 30000);
console.log("CITIES", cities);
console.log({ selectedCity });
return (
<div style={{ marginTop: "3rem" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "20px",
}}
>
<div>List of cities</div>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ marginRight: "1rem" }}>
<ToggleState
render={({ isOpen, open, close }) => {
return (
<>
<button
type="button"
className="btn btn-primary"
onClick={() => {
isOpen ? close() : open();
}}
>
Advanced Filter
</button>
<Modal
isActive={isOpen}
modalContentWidth={"30%"}
header={() => "Advanced Filter"}
close={() => close()}
renderBody={() => {
return <AdvancedFilter close={() => close()} />;
}}
></Modal>
</>
);
}}
/>
</div>
<div style={{ position: "relative", marginRight: "1rem" }}>
<input
type="text"
placeholder="Filter"
name="namePrefix"
style={{ padding: "0.35rem" }}
onChange={(e: any) => {
setFilter(e.target.value);
}}
/>
<div style={{ position: "absolute", top: "5px", right: "5px" }}>
<BsSearch size="16" />
</div>
</div>
<div style={{ width: "8rem" }}>
<div className="btn-group">
<button
type="button"
className="btn dropdown-toggle sort-button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{sortType === "asc"
? "Ascending"
: sortType === "desc"
? "Descending"
: "Select"}
</button>
<ul className="dropdown-menu sort-button">
<li>
<button
className="dropdown-item"
type="button"
onClick={() => onSort("asc")}
>
Ascending
</button>
</li>
<li>
<button
className="dropdown-item"
type="button"
onClick={() => onSort("desc")}
>
Descending
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
<div>
<div>
<div className="row">
{cities &&
sorted.map((item: any, index: number) => (
<div className="col-lg-3" key={index}>
<div
className="card"
style={{
textAlign: "center",
display: "flex",
justifyContent: "center",
paddingBottom: "1rem",
marginBottom: "1rem",
marginRight: "1rem",
}}
>
<div className="card-body">
<h5 className="card-title">{item.name}</h5>
</div>
{visible && showMeta ? (
<div>
<p>Longitude: {item.longitude}</p>
<p>Latitude: {item.latitude}</p>
<p>Population: {item.population}</p>
{/* <p>Time Zone: America</p> */}
</div>
) : (
<div
onClick={() => {
handleClick();
getSelectedCity(item.id);
}}
style={{ cursor: "pointer" }}
>
<FaPlus size="18" />
</div>
)}
</div>
</div>
))}
</div>
</div>
</div>
<div
style={{ marginTop: "30px", display: "flex", justifyContent: "center" }}
>
{cities && cities.length > 10 ? (
<button className="secondary-button">Load More</button>
) : (
<p>There are no more cities</p>
)}
</div>
</div>
);
}
here is the useTimeout function
import { useEffect, useRef } from "react";
function useTimeout(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) {
return;
}
const id = setTimeout(() => savedCallback.current(), delay);
return () => clearTimeout(id);
}, [delay]);
}
export default useTimeout;
Now currently if I click on one card, all the cards opens and also after when the data disappears after 30sec it does not reappear on button click. I need to reload the page to do reappear data again.I need to solve 2 issues here: 1. how can I open one card only at a time on clicking on the icon, 2. how can I reappear data on button click without refreshing the page.
As I understand you, some information is
<p>Longitude: {item.longitude}</p>
<p>Latitude: {item.latitude}</p>
<p>Population: {item.population}</p>
You have global statement of showing this info for all cards. To reduce it, make new component which will have local state of this info:
import React from 'react'
const timeout = 15000
export const CityCard = ({item, visible, getSelectedCity}) => {
const [showMeta, setShowMeta] = React.useState(false)
const handleClick = React.useCallback(()=>{
setShowMeta(true)
setTimeout(()=>{
setShowMeta(false)
},timeout )
}, [showMeta])
return(
<div className="col-lg-3" key={index}>
<div
className="card"
style={{
textAlign: "center",
display: "flex",
justifyContent: "center",
paddingBottom: "1rem",
marginBottom: "1rem",
marginRight: "1rem",
}}
>
<div className="card-body">
<h5 className="card-title">{item.name}</h5>
</div>
{visible && showMeta ? (
<div>
<p>Longitude: {item.longitude}</p>
<p>Latitude: {item.latitude}</p>
<p>Population: {item.population}</p>
{/* <p>Time Zone: America</p> */}
</div>
) : (
<button
onClick={() => {
handleClick();
getSelectedCity(item.id);
}}
type='button'
disabled={showMeta}
style={{ cursor: "pointer" }}
>
<FaPlus size="18" />
</button>
)}
</div>
</div>
)
}
At the end, add this component to the CityList

How to create a custom button in react-pdf-viewer?

I'm using react-pdf-viewer in a React application and need to customise the button images to suit the style of the app. I can get the viewer to display the correct button style but the button then does not do anything when it is clicked, nor does it display the tooltip when hovered over. I've tried attaching onClick functions to the various elements of the button but they only fire on rendering and not when clicked.
This is the main code for the viewer; I'm first trying to get the download button to work and will then use the solution for the other buttons.
import { Viewer, Worker } from '#react-pdf-viewer/core';
import { defaultLayoutPlugin } from '#react-pdf-viewer/default-layout';
import { zoomPlugin } from '#react-pdf-viewer/zoom';
import { getFilePlugin } from '#react-pdf-viewer/get-file';
import { DownloadButton } from './Components/Buttons';
import '#react-pdf-viewer/core/lib/styles/index.css';
import '#react-pdf-viewer/default-layout/lib/styles/index.css';
import '#react-pdf-viewer/toolbar/lib/styles/index.css';
const App = () => {
const zoomPluginInstance = zoomPlugin();
const { ZoomPopover } = zoomPluginInstance;
const getFilePluginInstance = getFilePlugin();
const { Download } = getFilePluginInstance;
const renderToolbar = (Toolbar) => (
<Toolbar>
{(slots) => {
const { NumberOfPages, CurrentPageInput, ZoomIn, ZoomOut, Print } = slots;
return (
<div
style={{
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
marginRight: 40
}}
>
<div style={{display: 'flex', alignItems: 'center'}}>
<div style={{ padding: '0px 5px' }}>
<CurrentPageInput style={{fontSize: 16}} />
</div>
<div style={{ padding: '0px 5px 3px' }}>
/ <NumberOfPages /> pages
</div>
</div>
<div className="zoomControls" style={{display: 'flex', alignItems: 'center'}}>
<div style={{ padding: '0px 5px' }}>
<ZoomOut />
</div>
<div style={{ padding: '0px 5px', backgroundColor: '#fff', borderRadius: 4 }}>
<ZoomPopover />
</div>
<div style={{ padding: '0px 5px' }}>
<ZoomIn />
</div>
</div>
<div style={{display: 'flex', alignItems: 'center'}}>
<div style={{ padding: '0px 5px', marginLeft: 'auto' }}>
<Print />
</div>
<div style={{ padding: '0px 5px' }}>
<Download>
{() => (
<DownloadButton />
)}
</Download>
</div>
</div>
</div>
);
}}
</Toolbar>
);
const defaultLayoutPluginInstance = defaultLayoutPlugin({
renderToolbar,
sidebarTabs: (defaultTabs) => [defaultTabs[0]],
});
return (
<Worker workerUrl="https://unpkg.com/pdfjs-dist#2.9.359/build/pdf.worker.js">
<div className="pdfContainer">
<Viewer
fileUrl={'https://file-examples-com.github.io/uploads/2017/10/file-example_PDF_1MB.pdf'}
plugins={[defaultLayoutPluginInstance, zoomPluginInstance, getFilePluginInstance]}
/>
</div>
</Worker>
);
};
export default App;
and this is the imported download button
export const DownloadButton = () => (
<button className="downloadButton" aria-label="Download">
<img src={process.env.PUBLIC_URL + '/icons/download.svg'} className="btButton" alt="Download" />
</button>
);
As far as the downloading the file, it seems that you've forgotten to use the props of the download button.
What you need to do is add the props and then pass the onClick handler to the DownloadButton like so:
<Download>
{(props) => (
<DownloadButton onCLick={props.onClick} />
)}
</Download>
Since your DownloadButton is a wrapper around a button you also need to define onClick as a prop there and then pass it down to the HTML button element.
/**
* A button used to download the a PDF.
*
* #param {Object} [props]
* #param {*} [props.onClick] The #react-pdf-viewer/get-file click handler.
*/
export const DownloadButton = (props) => {
return (
/** Pass the download button's `onClick` handler to this button. */
<button
onClick={props.onClick}
className="downloadButton"
aria-label="Download"
>
<img
src={process.env.PUBLIC_URL + "/icons/download.svg"}
className="btButton"
alt="Download"
/>
</button>
);
};
As far as the tooltip goes, it seems that you'll have to implement that yourself unless you use the default DownloadButton provided by `#react-pdf-viewer/get-file.

Rendering component on long mouse click

I am trying to render a modal component on a long mouse click. If I just try to fire an alert it works but rendering doesn't seem to do the trick. I am assuming maybe If I have to return? Not quite sure. I created a function handleButtonPressDown to perform this task and the handleButtonRelease to clear interval in the event the user decides not to perform this action.
export class Dropdown extends React.Component<IProps> {
buttonPressTimer: any;
constructor(props: IProps) {
super(props);
this.handleButtonPress = this.handleButtonPress.bind(this);
this.handleButtonRelease = this.handleButtonRelease.bind(this);
}
public render() {
return (
<div style={{ alignSelf: "center" }}>
<ul className="nav nav-pills">
{filteredProbes.length === 0 ? (
<li className="nav-item dropdown ">
<div
className="dropdown-menu show"
x-placement="bottom-start"
style={{
display: "none"
}}
></div>
</li>
) : (
<li className="nav-item dropdown ">
<div
className="dropdown-menu show"
x-placement="bottom-start"
style={{
position: "relative",
willChange: "transform",
top: "5px",
overflowY: "scroll",
maxHeight: "200px",
color: "white"
}}
>
{this.props.searchState.isActive === false
? probes.map(probe => (
<a
onClick={() => this.props.onUpdateSelectedProbe(probe)}
className="dropdown-item"
onMouseDown={this.handleButtonPress}
onMouseUp={this.handleButtonRelease}
>
<div
className="dropdown-divider"
style={{ backgroundColor: "black" }}
></div>
{probe.companyPN}: {probe.description}
</a>
))
: filteredProbes.map(filterprobe => (
<a
onClick={() =>
this.props.onUpdateSelectedProbe(filterprobe)
}
className="dropdown-item"
>
<div className="dropdown-divider"></div>
{filterprobe.companyPN}: {filterprobe.description}
</a>
))}
</div>
</li>
)}
</ul>
</div>
);
}
handleButtonPress() {
this.buttonPressTimer = setTimeout(() => {
{/* Show the modal if showModal is true */}
this.props.modalState.showModal && (
<WedgeGroup
wedgeState={this.props.wedgeState}
onUpdateSelectedWedge={this.props.onUpdateSelectedWedge}
onUpdateShowModal={this.props.onUpdateShowModal}
onUpdateHideModal={this.props.onUpdateHideModal}
modalState={this.props.modalState}
/>
);
}, 1000);
}
handleButtonRelease() {
clearTimeout(this.buttonPressTimer);
}
}
You need to move the code that you have inside setTimeout to render function and use state to render WedgeGroup:
export class Dropdown extends React.Component<IProps> {
...
constructor(props: IProps) {
super(props);
this.state = {
showModal: false
};
...
}
public render() {
const showModal = this.props.modalState.showModal &&
this.state.showModal;
return (
<div style={{ alignSelf: "center" }}>
{
showModal && (
<WedgeGroup
wedgeState={this.props.wedgeState}
onUpdateSelectedWedge={this.props.onUpdateSelectedWedge}
onUpdateShowModal={this.props.onUpdateShowModal}
onUpdateHideModal={this.props.onUpdateHideModal}
modalState={this.props.modalState}
/>
);
}
//..... render other components
</div>
);
}
handleButtonPress() {
this.buttonPressTimer = setTimeout(() => {
this.setState({
showModal: true
})
}, 1000);
}
handleButtonRelease() {
clearTimeout(this.buttonPressTimer);
}
}
It will not render firstly because your are not triggering any mechanism that makes React render.
I'd suggest to you to remove this component from the setTimeout, place it inside the render (where it should be).
And finally manipulate your component state to show or hide your modal.
If you trigger a timer to show the modal view it will only appear after the change of the state, so in your case it will take 1s to show to the user, what may look not responsive.
// inside your handleButtonPress()
this.setState({
showModal: true
}}

pass value of clicked list to EditForm to edit

I am doing reactjs and redux for developing dashboard. I have done add, delete but editing is not working. When user clicks on item, the textfield should display with its current value and able to submit the changes. I can show textfield when clicked but could not show the current value of that item which is clicked. To display textField i have to use onClick on the li tag otherwise i could pass data like using this.props.editTab(tab). How can i now send data of clicked item to editTab action ?
constructor(props) {
super(props);
this.state = { open: false, editing: false };
}
editTab() {
const tabs = _.map(this.props.tabs, (tab) => {
if (tab.editable) {
return (
<li
className="list-group-items delete-tab-list"
onClick={() => this.setState({ editing: true })}
key={tab.id}
>
<i className="material-icons">{tab.icon}</i>{tab.name}
</li>
);
}
});
return (
<div className="device-action">
<Dialog
title="Update a Tab"
modal={false}
bodyStyle={{ background: '#fff' }}
contentStyle={customContentStyle}
actionsContainerStyle={{ background: '#fff' }}
titleStyle={{ background: '#fff', color: '#1ab394' }}
open={this.props.createTab.open}
onRequestClose={this.props.closeTabIcon}
>
<ul className="list-group">
{ this.state.editing ?
<EditForm
tab={this.props.tabs}
editing={this.state.editing}
/> :
tabs
}
</ul>
</Dialog>
</div>
);
}
handleEditSave = (name, icon) => {
this.props.editTab(name, icon);
}
render() {
return (
<div>
<form onSubmit={this.handleEditSave}>
<div className="tab-name">
<TextField
floatingLabelText="Name"
onChange={(name) => { this.setState({ name: name.target.value }); }}
/>
</div>
<div className="icon">
<AutoComplete
floatingLabelText="select any icon"
filter={AutoComplete.noFilter}
openOnFocus
onNewRequest={(e) => { this.setState({ icon: e.id }); }}
/>
</div>
<button className="btn">Save</button>
</form>
</div>
);
}
How can i pass clicked item data to EditForm component so i can trigger my action in this.props.editTab(tab) this way ?
You can simply track the tab you editing by saving it on the state.
This will work only if you want to edit 1 tab at time. otherwise you can use Object/Array.
constructor(props) {
super(props);
this.state = { open: false, editing: null };
}
editTab() {
const tabs = _.map(this.props.tabs, (tab) => {
if (tab.editable) {
return (
<li
className="list-group-items delete-tab-list"
onClick={() => this.setState({ editing: tab })}
key={tab.id}
>
<i className="material-icons">{tab.icon}</i>{tab.name}
</li>
);
}
});
const { editing } = this.state;
// editing is the Tab object that we edit
if (editing)
console.log("Editing tab: " + editable.name);
return (
<div className="device-action">
<Dialog
title="Update a Tab"
modal={false}
bodyStyle={{ background: '#fff' }}
contentStyle={customContentStyle}
actionsContainerStyle={{ background: '#fff' }}
titleStyle={{ background: '#fff', color: '#1ab394' }}
open={this.props.createTab.open}
onRequestClose={this.props.closeTabIcon}
>
<ul className="list-group">
{ this.state.editing ?
<EditForm
tab={this.props.tabs}
editing={this.state.editing}
/> :
tabs
}
</ul>
</Dialog>
</div>
);
}
handleEditSave = (name, icon) => {
this.props.editTab(name, icon);
}
render() {
return (
<div>
<form onSubmit={this.handleEditSave}>
<div className="tab-name">
<TextField
floatingLabelText="Name"
onChange={(name) => { this.setState({ name: name.target.value }); }}
/>
</div>
<div className="icon">
<AutoComplete
floatingLabelText="select any icon"
filter={AutoComplete.noFilter}
openOnFocus
onNewRequest={(e) => { this.setState({ icon: e.id }); }}
/>
</div>
<button className="btn">Save</button>
</form>
</div>
);
}

Categories

Resources