jbetancur / react-data-table-component

A responsive table library with built-in sorting, pagination, selection, expandable rows, and customizable styling.
https://react-data-table-component.netlify.app
Apache License 2.0
2.06k stars 413 forks source link

Close expanded row with an onclick event #690

Closed smammadov1994 closed 3 years ago

smammadov1994 commented 4 years ago

Hello, I am fairly new to datatables. I am building an application and have hit a roadblock. I need to grab the unique id of a note when clicking on the expanded row. I managed to do this but when I click on a delete button, it deletes that row but the next row has its expanded component open. How do I close the expanded component programatically?

jbetancur commented 4 years ago

This is not currently possible since the Expander events are not exposed in the public API. There are plans to refactor expanders to allow this but I don't have an ETA.

geudrik commented 4 years ago

I'd also love to see this functionality exposed. onClick events for the whole row would be a good thing as well (eg: for handling multi-row selection without the need to individually click check boxes)

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

feiraelectronica commented 3 years ago

I'd also love to see this functionality exposed. onClick events for the whole row would be a good thing as well (eg: for handling multi-row selection without the need to individually click check boxes)

hi @geudrik, have you found the solution for this?

geudrik commented 3 years ago

I'd also love to see this functionality exposed. onClick events for the whole row would be a good thing as well (eg: for handling multi-row selection without the need to individually click check boxes)

hi @geudrik, have you found the solution for this?

Yeah, kind of. It's more of a hack than anything, and the state bubbling up issue (need to find it again) for custom cells is indeed an issue.

I'll post code in a little bit, still going strong on coffee.

geudrik commented 3 years ago

Pre-coffee scumbag brain lied to me (and/or I still can't read). We don't do row toggling by clicking anywhere on the row - we handle row selection doing that (as well as range-selection, akin to what DataTables does). Here's the code for that

function rdt_func_onRowClicked(row, e) {
    let shift_key_pressed = e && e.shiftKey || false;

    // We're going to store the index of this row as our "last row clicked", since we need that to compute ranges
    let index_of_row_we_just_clicked = this.state.data.findIndex((stateful_row_data) => {
        return stateful_row_data[this.constructor.id_key_name] === row[this.constructor.id_key_name]
    })

    this.setState((prev) => {

        let data = prev.data;

        if(shift_key_pressed) {
            let low = prev.last_row_clicked;
            let high = index_of_row_we_just_clicked;
            if(prev.last_row_clicked >= index_of_row_we_just_clicked) {
                low = index_of_row_we_just_clicked;
                high = prev.last_row_clicked;
            }

            let updated_rows = Array(high - low + 1).fill().map((_, idx) => low + idx).reduce((acc, i) => {
                acc[i] = {rdt_selected: {$set: true}};
                return acc
            }, {})
            data = update(prev.data, updated_rows)
        }

        // Shift key is NOT pressed, so we're going to select/de-select a single row
        else {
            let currently_selected_state = !!prev.data[index_of_row_we_just_clicked].rdt_selected;
            data = update(prev.data, {[index_of_row_we_just_clicked]: {rdt_selected: {$set: !currently_selected_state}}})
        }

        return {
            data: data,
            last_row_clicked: index_of_row_we_just_clicked
        };
    }, function() {

    })
}

Full disclosure: this is definitely a hack and should be exposed as part of the core Lib, but hey, sometimes hacky is good, right? 😁

We don't do row-clicking to toggle folding. We rely on selection/multi-selection using ranges (eg: holding shift to select a range of rows). This lets us do that.

This basically lets you pass in a callback to the underlaying Tabling lib onRowClicked prop that alters state, etc based on what's going on. This lets you not only bypass using checkboxes for row selection entirely, but also do things like register click events anywhere on the row, with the one caveat #684

To cause a row to open on click, you'd (somewhere in there, whip that debugger out) you'd just need to patch into the following two callback methods, similar to what we've done above for row selection. And of course, negate the shift-range selection bits.

// Ensure that we're overriding the the built-ins
//  See: https://www.npmjs.com/package/react-data-table-component#row-expander for docs
table_props['onRowExpandToggled'] = this.on_row_expand_toggle;
table_props['expandableRowExpanded'] = this.expandable_row_expanded;

function on_row_expand_toggle(is_expanded, row_data) {
    // This function overrides the built-in from the lib we're using. It's called every time we
    //  click a row to expand it. It must return a tuple (bool_is_expanded, obj_row_data)

    if (is_expanded) {
        // If we expanded the row, we need to add the row data to the state var that tracks open rows
        this.setState({expanded_rows: update(this.state.expanded_rows, {$push: [row_data]})});
    } else {
        // The row is closing, remove the reference from the open_rows state var
        const delete_idx = this.state.expanded_rows.findIndex(i => Object.is(i, row_data));
        if (delete_idx >= 0) {
            this.setState({expanded_rows: update(this.state.expanded_rows, {$splice: [[delete_idx, 1]]})});
        }
    }

    return [is_expanded, row_data]
}

function expandable_row_expanded(row_data) {
    // This function overrides the built-in from the lib we're using. It's called every time
    //  the renderer questions whether or not a given row should be open (eg: on state/context change)
    //  In our case, we use to check to see if the given row is in the open list, and if so, return true
    //  hopefully ensuring that the row doesn't get removed from the DOM by React

    // Return true if the row is in the currently expanded row list
    return this.state.expanded_rows.some(i => Object.is(i, row_data));
}

Note that all of these are instance methods we pass as callbacks to the pertinent props to react-data-table-component, not raw functions as you see in the code blocks.

Hope this helps!

smammadov1994 commented 3 years ago

Is this supposed to stop the next row from expanding? I cannot really understand the code or where to put it.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.