I am using datatables.net plugin with React. I generate a button in the last column of every row using the following parameter in columnDefs of the datatables:
{
"targets": [10],
createdCell: (td, cellData, rowData, row, col) => {
ReactDOM.render(
<button onClick={props.handleReportClick}></button>
, td);
}
})
Within my react page, I have the following:
const handleReportClick = (e) =>{
e.stopPropagation();
console.log(dataTable);
};
The console.log above gives me empty string (there is a hook [dataTable, setDataTable] on the page which has values immediately after initiation of the datatable) and same happens for all other hook values on that page. All other event handlers on the page console.log(dataTable) as an object and can access all state but not this one.
Any help appreciated.
I have found that passing reference to the handler function to the button (as in my example above) was giving me all sorts of problems with the handler function referencing wrong render instances. Instead, I provided each button with a unique data-id (in my case row was enough as I only have one button in a row) and then created a useEventListener hook to add event listener to all clicks on the page. I also added the below function to get the data-id of the button that was clicked as the event listener was unreliable and yielded child elements as targets. Below works well and captures data-id each time. This way, the state that I access for dataTable is correct and I can capture info from datatable's appropriate row and process it.
export default function getSelectedRow(e) {
let parent = e.target;
let selectedRow;
while (parent) {
if (parent.getAttribute('data-id')) {
selectedRow = parent.getAttribute('data-id');
}
parent = parent.parentElement;
}
return selectedRow;
}
Related
I am using Ag-grid library for grid view in React app. Following is my Ag-Grid component:
const handleChanged = (gridOptions) => {
const selectedNodes = gridOptions.api.getSelectedNodes()
//TODO
}
<AgGridReact
data-testid="details-data"
columnDefs={DetailsColDef}
rowData={formatDetailsData(
data?.Response,
false
)}
rowSelection="single"
reactNext={true}
defaultColDef={defaultColDef}
onSelectionChanged={handleSelected}
suppressPaginationPanel={true}
domLayout="autoHeight"
suppressMaxRenderedRowRestriction={true}
rowBuffer={5}
suppressColumnVirtualisation={false}
debounceVerticalScrollbar={true}
alwaysShowVerticalScroll={true}
></AgGridReact>
Current Scenario: handleChanged is getting called when we click on Grid row.
Requirement: Need to call handleChanged event every time on multiple click at same time. Currently event is getting called first time only. If we click again on same row, it need's to be called.
You can call gridOptions.api.getSelectedNodes() inside onCellClicked. Every time the row/cell is clicked you will get the data
const onCellClicked = (e) => {
const selectedRows = gridOptions.api.getSelectedNodes();
console.log(selectedRows)
}
<AgGridReact
onCellClicked={onCellClicked}
....
>
</AgGridReact>
Check working example
I think you are trying to use onSelectionChanged out of its scope, if you need to see if a cell is clicked (any number of times, you can use the onCellClicked Event). there are multitudes of events that can be used if that doesn't suit your needs.
https://www.ag-grid.com/react-data-grid/grid-events/#reference-selection
Otherwise if you are going to use this event only one handy way would be to change the selected row at the end of your processing and making this process async. this would be be problematic if the user clicks the row you have chosen to set it to so not the optimal solution i would say.
https://www.ag-grid.com/react-data-grid/row-selection/#node-selection-api
I am having trouble preventing unnecessary side-effects from a useEffect hook linked to a component that gets re-rendered/reused multiple times after being clicked. I want the side-effect to trigger an action once, but it is being triggered for every render.
I'm building a task viewer that displays nested task data. Please see screenshot #1 for reference.
For context, the main display shows the main tasks (see Event Data) and renders a clickable button if the task has sub-events. When this button is clicked, the selected main task is displayed at the top of the hierarchy view (see Event Hierarchy) and its sub-events are displayed below the hierarchy in a separate pane (see Event Details).
Like the main tasks, if these sub-events in 'Event Details' have their own sub-events, they are also rendered with a clickable button. When this sub-event button is clicked, this clicked sub-event is added to the bottom of the hierarchy, where the clicked main task is already displayed in bold. This selected sub-event's sub-events then replace the content in the 'Event Details' pane.
As the user clicks through the nested data, the clicked sub-event is added to the bottom of the hierarchy so that the user has an idea of where he is in the nested data and its sub-events displayed in 'Event Details'. All 'Event Hierarchy' and 'Event Details' data is cleared when the user selects a new main event or selects a new page.
The hierarchy events are held in an array managed via useState and every time another sub-event is clicked, it is added to this array. That's the idea, at least.
#1
My problem is this:
If I place my setHierarchy function inside a useEffect hook with the selectedTask as dependency, it renders the selectedTask in the hierarchy instantaneously, but the button component that triggers setHierarchy is re-rendered for every sub-event being displayed in 'Event Details' (as I want each event to be clickable) and in doing so, it adds that many copies of the event to my hierarchy array. This happens even though I am checking to see if the hierarchy array already contains the selected subevent before adding it. See result in screenshot #2.
I have tried various configurations of checking the array, but I cannot seem to stop it from adding these copies to and subsequently displaying them in the Hierarchy.
If I place the setHierarchy function inside my click handler, only one single event is added, but it executes before the selectedSubEvent has been updated. This means the hierarchy array is empty upon first render and stays one click 'behind' ie. a specific event is only displayed upon the following click event, after the click that selected it.
#2
This is all done inside my ExpandSubEvents button component (see code below) and also managed via a context provider.
I have tried moving the setHierarchy into a separate function, inside a useCallback, and triggering it from both the clickHandler and the useEffect that sets the selectedSubEvent. This did not resolve the issue.
I've also tried useRef to try and link it to the latest state. I'm not sure that's even doable/correct.
What am I doing wrong here? I am fairly new to coding, so any input on this would be much appreciated.
Sidenote: I suspect that my setup is perhaps beyond the intended scope of useContext. Is it? What can I do to make improvements? Is this perhaps in any way responsible for my issue?
Thank you for taking your time to read this far. I appreciate it!
Deon
ExpandSubEvents Component
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
import SubEventContext from '../../store/sub-event-context';
import classes from './ExpandSubEvents.module.css';
const ExpandSubEvents: React.FC<{
id: number;
subEvents: number;
}> = React.memo((props) => {
// Extract context
const subEventCtx = useContext(SubEventContext);
const {
subEvents,
subEventParentId,
selectedSubEvent,
hierarchy,
setSubEventParentId,
setFetchId,
setSelectedSubEvent,
setHierarchy,
} = subEventCtx;
// Get id of event for when it is clicked
const id = React.useMemo(() => props.id, [props.id]);
let eventIds: number[] = useMemo(() => [], []);
if (hierarchy) {
for (const event of hierarchy) {
eventIds.push(event.id);
}
}
// Set CSS classes to style button if it has sub-events
let subEventQuantity = props.subEvents;
let importedClasses = `${classes['sub-event-button']}`;
if (subEventQuantity === 0) {
importedClasses = `${classes['no-sub-events']}`;
}
// Push the event to the Hierarchy display
// NOTE Tried moving the setHierarchy to a separate function, but it did not make a difference
// const triggerHierarchy = useCallback(() => {
// if (!eventIds.includes(id))
// setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
// }, [eventIds, id, selectedSubEvent, setHierarchy]);
// Respond to subevent button click event
const clickHandler = useCallback(() => {
setSubEventParentId(id);
setFetchId(id);
// This setHierarchy works, but executes before the selectedSubEVent has been updated
// Furthermore, if a new subevent is selected, it checks if the NEW clicked one has been added
// BUT sends the OLD event still in selectedSubEvent to the hierarchy before IT has been updated
// meaning that the check does not stop the same event being added twice
if (!eventIds.includes(id))
setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
}, [
eventIds,
id,
selectedSubEvent,
setFetchId,
setHierarchy,
setSubEventParentId,
]);
// NOTE Tried useRef to get setHierarchy to use the latest selectedSubEvent
// const subEventRef = useRef<Event[]>([]);
// subEventRef.current = hierarchy;
// Trying to setHierarchy directly from its own useEffect
// useEffect(() => {
// if (!eventIds.includes(id))
// setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
// }, [eventIds, hierarchy, id, selectedSubEvent, setHierarchy]);
// Filter the event from the subEvent array and set it to selectedSubEvent
useEffect(() => {
setSelectedSubEvent(
subEvents.filter((subEvent) => subEvent.id === subEventParentId)
);
}, [setSelectedSubEvent, subEventParentId, subEvents]);
return (
<button onClick={clickHandler} className={importedClasses}>
{subEventQuantity}
</button>
);
});
export default ExpandSubEvents;
I use the dayHeaderContent prop from Full Calendar V5 to Inject certain extra content, Whenever I change or update an event and whenever the calendar re renders, the dayHeaderContent prop is being called and my new content is displayed on the top, It all works fine.
The issue comes when I update the event using the eventDrop and eventResize, The event card itself updates fine, but the dayHeaderContent on the top does not re render, until I manually re fetch the events or click the next page and come back to current page.
Is there a way I can make the dayHeaderContent render eveytime eventDrop and eventResize is used? I know I could just use the calendar refresh or refetch event source, But I cannot do that because I am supplying just one single event source static array of events, So I cannot use the refresh methods.
// Here's how I am supplying my events, here data is a static array
calendarRef.current.getApi().addEventSource(data)
// I want the below Inject function to run eveytime an event is update via eventDrop and eventResize props
const injectDayHeaderContent = (args) => {
if (args.view.type === 'timeGridWeek') {
return (
<span>
{convertDecimalToHHMM(showHours)}
</span>
)
}
}
return(
<FullCalendar
.....
.....
dayHeaderContent={injectDayHeaderContent}
/>
)
I have a list of styled radio buttons fixed inside the sidebar that, when clicked, scroll a corresponding component from the main area into view.
Both the radio buttons list and the components inside the main area are mapped from the state array called usedComponents that contains objects of component properties and ids. Let's say the array contains 10 objects and each object looks something like this:
{
id: 1,
group: "header",
componentType: "headerLogo",
componentName: "Header 01",
//...rest of the component properties
}
In order to achieve scroll into view on radio button click I had to create references to components inside the constructor. I manually created references for each component contained inside usedComponents state array and tied them to componentType like this:
this.headerLogo = React.createRef();
this.headerLogoNavigation = React.createRef();
//...etc.
I then passed the reference to each corresponding component and in my selectComponentHandler I set scrollIntoView to call the radio button id which corresponds to the main area component reference.
The selectComponentHandler looks like this:
selectComponentHandler = e => {
const value = Number(e.target.value);
const id = e.target.id; //returns componentType. For example "headerLogo"
this.setState(prevState => {
let selected = prevState.usedComponents
.filter(item => item.id === value)
.shift();
let unSelected = prevState.usedComponents
.filter(item => item.selected === true)
.shift();
if (unSelected) {
unSelected.selected = false;
}
selected.selected = true;
return { unSelected, selected };
});
this[id].current.scrollIntoView({ block: "start", behavior: "smooth" });
};
However, I'm using react-beautiful-dnd in order to be able to add new components by dragging them to the main area, and whenever I add the new component of the type that is already contained inside the usedComponents array, it has the same reference as the old component of the same type. This makes the scrollIntoView always scroll to the component of the same type that is first in line, no matter which one I select.
Is there a way to create references dynamically for all the components inside the usedComponents array, by perhaps mapping them, and update them whenever a new component is inserted. Maybe tie them to ids?
Edit:
I tried mapping references inside the constructor like this:
this.state.usedComponents.map(component => {
const id = component.id.toString();
return (this[id] = React.createRef());
});
It works for the components that are already inside the used Components array, however I still don't know how to update the references when the new object is inserted into the usedComponents array via drag and drop. Newly inserted components basically don't have references.
Like I was mentioning in the comments, I wouldn't go the route of dynamic refs. The scope / size of these refs could grow large and there is a much better way to handle this. Just use the DOM.
When rendering you can just put the components ID on your html element that you want to scroll to
<div id={component.id}> ... </div>
And then when you want to scroll to that element just query the DOM for that element and scroll to it
const elemToScrollTo = document.getElementById(component.id)
if (!!elemToScrollTo) {
elemToScrollTo.scrollIntoView()
}
I recently started learning React, I have a data grid in my component, On click of a Row an event is triggered. In an event handler I could fetch the Row props (values from each column of a row clicked). So what I wanted to achieve is: I have 3 buttons on Form Component which gets enabled onClick of Row in the Grid. I want to navigate to new page onClick of each button wherein I will be performing different actions for the Row selected in a grid. The problem which I am facing is, How can I pass Row props to the handler of all 3 buttons as I need the Row values to perform different operations using these buttons.
onRowclick = (row) =>
{
this.context.route.push(`path\${row.ID}`);
}
onButton1click = (row) =>
{
this.context.route.push(`path\${row.ID}`);
}
.
.
.
onButton3click = (row) =>
{
this.context.route.push(`path\${row.ID}`);
}
I need the row.ID in all three event handlers, is their some way to store this in a state so that I will be able to use this throughout the component.?
UPDATE: Below is the code to achieve this
constructor(props) {
super(props);
this.state = this.getInitState(props);
this.intitialize();
}
intitialize(){
this.selectedRow = {};
}
getInitState(props) {
return {selectedRow: undefined};
}
// on Row click storing the row object(which contains row data)
onRowClick = (row) => {
this.selectedRow = row;
}
very basic of React
I think it's time to use REDUX.
Redux is a react framework and is used to store state in an object. Redux makes the tree of objects and you can retrieve any state through the application by using the reducer and storing state with the help of action.
Redux have a store where it stores all the state of the application.
To know full detail about redux please read this: http://redux.js.org/