I am new to react-grid-layout, I have created a sample example with drag and drop using this example https://react-grid-layout.github.io/react-grid-layout/examples/15-drag-from-outside.html. When the item is dropped it’s always placed to the first column of the grid. X and Y are correct from the alert,
What is wrong in my code?
This is my code:
onDrop = (layout, layoutItem, _event) => {
alert(`Dropped element props:\n${JSON.stringify(layoutItem, ['x',
'y', 'w', 'h'], 2)}`);
this.setState(prevState => ({
layouts: {
...prevState.layouts,
[prevState.currentBreakpoint]: [
...prevState.layouts[prevState.currentBreakpoint],
layoutItem
]
}
}));
};
render() {
return (
<div>
<div className="toolBar">
<div
className="droppable-element"
draggable={true}
unselectable="on"
>
Button 1
</div>
</div>
<div>
<ResponsiveReactGridLayout
{...this.props}
layouts={this.state.layouts}
onBreakpointChange={this.onBreakpointChange}
onLayoutChange={this.onLayoutChange}
onDrop={this.onDrop}
droppingItem={this.props.item}
measureBeforeMount={false}
useCSSTransforms={this.state.mounted}
compactType={this.state.compactType}
preventCollision={true}
isDroppable={true}
>
{this.generateDOM()}
</ResponsiveReactGridLayout>
</div>
</div>
);
}
I figured out. I added data-grid={el} to the element which should be dropped. This is the code.
import React from "react";
import _ from "lodash";
import { Responsive, WidthProvider } from "react-grid-layout";
const ResponsiveReactGridLayout = WidthProvider(Responsive);
export default class DragFromOutsideLayout extends React.Component {
static defaultProps = {
className: "layout",
rowHeight: 30,
onLayoutChange: function () { },
cols: { lg: 8, md: 5, sm: 6, xs: 4, xxs: 2 },
verticalCompact: false,
preventCollision: true
};
state = {
currentBreakpoint: "lg",
compactType: "horizontal",
mounted: false,
layouts: { lg: generateLayout() },
newCounter: 0
};
generateDOM(el) {
return (
<div key={el.i} data-grid={el}>
<span className="text">{el.i}</span>
</div>
);
}
onBreakpointChange = breakpoint => {
this.setState({
currentBreakpoint: breakpoint
});
};
onCompactTypeChange = () => {
const { compactType: oldCompactType } = this.state;
const compactType =
oldCompactType === "horizontal"
? "vertical"
: oldCompactType === "vertical"
? null
: "horizontal";
this.setState({ compactType });
};
onDrop = (layout, layoutItem, _event) => {
this.setState({
layouts: {
lg: this.state.layouts.lg.concat({
i: this.state.newCounter.toString(),
x: layoutItem.x,
y: layoutItem.y,
w: layoutItem.w,
h: layoutItem.h
})
},
newCounter: this.state.newCounter + 1
});
};
render() {
return (
<div>
<div className="toolBar">
<div
className="droppable-element"
draggable={true}
unselectable="on"
>
Button 1
</div>
</div>
<div className="space"></div>
<div className="gridL">
<span>Drop the item here</span>
<ResponsiveReactGridLayout
{...this.props}
onDrop={this.onDrop}
droppingItem={this.props.item}
measureBeforeMount={false}
useCSSTransforms={this.state.mounted}
compactType={this.state.compactType}
preventCollision={true}
isDroppable={true}
>
{_.map(this.state.layouts.lg, el => this.generateDOM(el))}
</ResponsiveReactGridLayout>
</div>
</div>
);
}
}
function generateLayout() {
return _.map(_.range(0, 0), function (item, i) {
var y = Math.ceil(Math.random() * 4) + 1;
return {
x: Math.round(Math.random() * 5) * 2,
y: Math.floor(i / 6) * y,
w: 2,
h: y,
i: i.toString(),
static: Math.random() < 0.05
};
});
}
const containerElement = document.getElementById('root');
ReactDOM.render(<DragFromOutsideLayout />, containerElement);
Related
I have a grid with data, and when I click on a draggable icon I want to take every selected element and store it inside an array. Currently, my code works, but for some reason currently, not all selected elements are saved in the array, some get stored others not in a random way. Why is this occurring and how can I push each item once its selected in the array? Here is my code:
import * as React from 'react';
import { ReorderContext } from './main';
import { useDraggable, useDroppable } from '#progress/kendo-react-common';
export const DraggableRow = (props) => {
const [dropped, setDropped] = React.useState(false);
const [dragged, setDragged] = React.useState(false);
const [direction, setDirection] = React.useState(null);
const [selectedItems] = React.useState([]);
const [initial, setInitial] = React.useState({
x: 0,
y: 0,
});
const { dragStart, reorder } = React.useContext(ReorderContext);
const element = React.useRef(null);
const handlePress = (event) => {
if (event.ctrlKey == true) {
element.current.style.color = 'red';
selectedItems.push(props.dataItem);
console.log(selectedItems);
}
setInitial({
x: event.clientX - event.offsetX,
y: event.clientY - event.offsetY,
});
};
const handleDragStart = (event) => {
if (
!event.originalEvent.target ||
!event.originalEvent.target.dataset.dragHandle
) {
return;
}
setDragged(true);
dragStart(props.dataItem);
};
const handleDrag = (event) => {
if (!element.current || !dragged) {
return;
}
element.current.style.transform = `translateY(${
event.clientY - initial.y + event.scrollY
}px)`;
};
const handleDragEnd = () => {
setDragged(false);
setDropped(false);
setInitial({
x: 0,
y: 0,
});
};
const handleRelease = () => {
if (!element.current) {
return;
}
element.current.style.transform = null;
};
const handleDragEnter = () => {
setDropped(true);
setDirection(null);
};
const handleDragOver = (event) => {
if (!element.current) {
return;
}
const rect = element.current.getBoundingClientRect();
setDirection(
rect.top + rect.height / 2 <= event.pageY ? 'after' : 'before'
);
};
const handleDragLeave = () => {
setDropped(false);
setDirection(null);
};
const handleDrop = () => {
reorder(props.dataItem, direction);
setDropped(false);
setDirection(null);
};
useDraggable(
element,
{
onPress: handlePress,
onDragStart: handleDragStart,
onDrag: handleDrag,
onDragEnd: handleDragEnd,
onRelease: handleRelease,
},
{
autoScroll: dragged,
}
);
useDroppable(element, {
onDragEnter: handleDragEnter,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
});
return (
<React.Fragment>
{dropped && direction === 'before' && (
<tr
style={{
outlineStyle: 'solid',
outlineWidth: 1,
outlineColor: 'red',
}}
/>
)}
<tr
{...props.elementProps}
ref={element}
style={{
backgroundColor: '#fff',
userSelect: 'none',
pointerEvents: dragged ? 'none' : undefined,
opacity: dragged ? '0.8' : undefined,
}}
/>
{dropped && direction === 'after' && (
<tr
style={{
outlineStyle: 'solid',
outlineWidth: 1,
outlineColor: 'red',
}}
/>
)}
</React.Fragment>
);
};
and here is a runnable example:
https://stackblitz.com/edit/react-cv2hnu-euptwm?file=app/draggable-row.jsx
I am trying to use Victory for my React project but it is not working for some reason.
The code I am using is:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.getData()
};
}
componentDidMount() {
this.setStateInterval = window.setInterval(() => {
this.setState({
data: this.getData()
});
}, 3000);
}
componentWillUnmount() {
window.clearInterval(this.setStateInterval);
}
getData() {
const bars = random(6, 10);
return range(bars).map((bar) => {
return {x: bar + 1, y: random(2, 10)};
});
}
render() {
return (
<VictoryChart
domainPadding={{ x: 20 }}
animate={{duration: 500}}
>
<VictoryBar
data={this.state.data}
style={{
data: { fill: "tomato", width: 12 }
}}
animate={{
onExit: {
duration: 500,
before: () => ({
_y: 0,
fill: "orange",
label: "BYE"
})
}
}}
/>
</VictoryChart>
);
}
}
ReactDOM.render(<App/>, mountNode)
where I have used most components in different parts of my function.
The error I am getting is:
Line 28:18: 'random' is not defined no-undef
Line 29:12: 'range' is not defined no-undef
Line 30:30: 'random' is not defined no-undef
Search for the keywords to learn more about each error.
I don't know what to import as I have just added the Victory components
Here's an alternate function to the one you were using, it wasn't valid javascript. Where'd you get it from?
const getData = () => {
let arr = [];
for (let i = 0; i < 15; i++) {
arr.push({ x: x[i], y: y[Math.random(2, 10)] });
}
return arr;
};
I have a network that I want to draw with Konva (and the react-konva bindings). When positions update I want to animate the nodes in the network to their new positions while also animating the start and end position of the link that connects them.
I started with the following simple example, but can't seem to get a Line to animate in the same way that the nodes do.
Is there a way to fix this, or am I approaching it in the wrong way?
import React from "react";
import { Stage, Layer, Rect, Line } from "react-konva";
class Node extends React.Component {
componentDidUpdate() {
this.rect.to({
x: this.props.x,
y: this.props.y,
});
}
render() {
const { id } = this.props;
const color = id === "a" ? "blue" : "red";
return (
<Rect
ref={node => {
this.rect = node;
}}
width={5}
height={5}
fill={color}
/>
);
}
}
class Link extends React.Component {
componentDidUpdate() {
const x0 = 0;
const y0 = 0;
const x1 = 100;
const y1 = 100;
this.line.to({
x: x0,
y: y0,
points: [x1, y1, x0, y0],
});
}
render() {
const color = "#ccc";
return (
<Line
ref={node => {
this.line = node;
}}
stroke={color}
/>
);
}
}
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: [{ id: "a", x: 0, y: 0 }, { id: "b", x: 200, y: 200 }],
links: [
{
source: "a",
target: "b",
},
],
};
}
handleClick = () => {
const nodes = this.state.nodes.map(node => {
const position = node.x === 0 ? { x: 200, y: 200 } : { x: 0, y: 0 };
return Object.assign({}, node, position);
});
this.setState({
nodes,
});
};
render() {
const { links, nodes } = this.state;
return (
<React.Fragment>
<Stage width={800} height={800}>
<Layer>
{nodes.map((node, index) => {
return (
<Node
key={`node-${index}`}
x={node.x}
y={node.y}
id={node.id}
/>
);
})}
</Layer>
<Layer>
{links.map(link => {
return (
<Link
source={nodes.find(node => node.id === link.source)}
target={nodes.find(node => node.id === link.target)}
/>
);
})}
</Layer>
</Stage>
<button onClick={this.handleClick}>Click me</button>
</React.Fragment>
);
}
}
export default Graph;
You may need to set initial values for points attribute for a better tween.
Also, you are not using the source and target in the Link component. You should use that props for calculating animations.
import React from "react";
import { render } from "react-dom";
import { Stage, Layer, Rect, Line } from "react-konva";
class Node extends React.Component {
componentDidMount() {
this.rect.setAttrs({
x: this.props.x,
y: this.props.y
});
}
componentDidUpdate() {
this.rect.to({
x: this.props.x,
y: this.props.y
});
}
render() {
const { id } = this.props;
const color = id === "a" ? "blue" : "red";
return (
<Rect
ref={node => {
this.rect = node;
}}
width={5}
height={5}
fill={color}
/>
);
}
}
class Link extends React.Component {
componentDidMount() {
// set initial value:
const { source, target } = this.props;
console.log(source, target);
this.line.setAttrs({
points: [source.x, source.y, target.x, target.y]
});
}
componentDidUpdate() {
this.animate();
}
animate() {
const { source, target } = this.props;
this.line.to({
points: [source.x, source.y, target.x, target.y]
});
}
render() {
const color = "#ccc";
return (
<Line
ref={node => {
this.line = node;
}}
stroke={color}
/>
);
}
}
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: [{ id: "a", x: 0, y: 0 }, { id: "b", x: 200, y: 200 }],
links: [
{
source: "a",
target: "b"
}
]
};
}
handleClick = () => {
const nodes = this.state.nodes.map(node => {
const position = node.x === 0 ? { x: 200, y: 200 } : { x: 0, y: 0 };
return Object.assign({}, node, position);
});
this.setState({
nodes
});
};
render() {
const { links, nodes } = this.state;
return (
<React.Fragment>
<Stage width={800} height={300}>
<Layer>
{nodes.map((node, index) => {
return (
<Node
key={`node-${index}`}
x={node.x}
y={node.y}
id={node.id}
/>
);
})}
</Layer>
<Layer>
{links.map(link => {
return (
<Link
source={nodes.find(node => node.id === link.source)}
target={nodes.find(node => node.id === link.target)}
/>
);
})}
</Layer>
</Stage>
<button onClick={this.handleClick}>Click me</button>
</React.Fragment>
);
}
}
render(<Graph />, document.getElementById("root"));
Demo: https://codesandbox.io/s/react-konva-animating-line-demo-erufn
I am trying to create a booking slot.I have store all the available time slot in state, I am getting the max_slots = number from API & from max_slots i am creating slots on each given time.Now i need to store selected Slots in state & Change the colour of selected Slots.Uncheck the slots when double clicked.
I have tried doing here is my code your most welcome for Edit.
https://codesandbox.io/s/2v15vl8rk0
class SlotBookingComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
bookingDate: "",
buttonColor: "red",
startTime: "",
endTime: "",
AvailableSlots: [],
slots: null
};
this.updateState = this.updateState.bind(this);
//this.handleSlots = this.handleSlots.bind(this);
}
onButtonPress() {
this.setState({
buttonColor: "#123eee"
});
}
parseIn(date_time) {
var d = new Date();
d.setHours(date_time.substring(11, 13));
d.setMinutes(date_time.substring(14, 16));
return d;
}
getTimeSlots(time1, time2) {
var arr = [];
while (time1 < time2) {
arr.push(time1.toTimeString().substring(0, 5));
time1.setMinutes(time1.getMinutes() + 30);
}
return arr;
}
updateState() {
var startTime = "2019-05-06T10:30:00+05:30";
var endTime = "2019-05-06T22:00:00+05:30";
startTime = this.parseIn(startTime);
endTime = this.parseIn(endTime);
var intervals = this.getTimeSlots(startTime, endTime);
this.setState({
AvailableSlots: intervals
});
}
componentDidMount() {
this.updateState();
}
componentWillReceiveProps(props) {
this.setState({ slots: props.SlotsData });
console.log(this.state.slots);
}
handleClick(i, item) {
this.setState({
slotsbooked: i,
time: item
});
console.log(i, item);
}
render() {
const AvailableSlots = this.state.AvailableSlots;
const handleSlots = (max_slot, item) => {
let slots = [];
for (let counter = 1; counter <= max_slot; counter++) {
slots.push(
<div
className="col"
onClick={() => this.handleClick(counter, item)}
style={{
margin: 5,
backgroundColor: "#575756",
height: "28px"
}}
/>
);
}
// console.log(slots);
return slots;
};
const RowData = AvailableSlots.map(function(item, index) {
//max_slot wold come from API
var max_slot = 4;
if (max_slot == 1) {
return (
<div className="row">
<p>{item}</p>
<div
className="col"
key={item}
style={{ backgroundColor: "#123eee", height: "28px" }}
>
col
</div>
</div>
);
} else {
return (
<div className="data">
<div className="row test">
<div className="slot">{item}- </div>
{handleSlots(max_slot, item)}
</div>
</div>
);
}
});
return (
<div className="container">
<div className="col-md-9 slot-window product-details">{RowData}</div>
</div>
);
}
}
ReactDOM.render(<SlotBookingComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
Make each time have it's own time slots in the AvailableSlots array. e.g. [{time: '20:00', slots: {slot1: false, slot2: false, slot3: true}].
getTimeSlots(time1, time2) {
var arr = [];
while (time1 < time2) {
arr.push({
time: time1.toTimeString().substring(0, 5),
// create slots for each given time
// change slots object properties to your liking
slots: {
'1': false,
'2': false,
'3': false,
'4': false
}
});
time1.setMinutes(time1.getMinutes() + 30);
}
return arr;
}
Handle on click
handleClick(i, item, index) {
const selected = this.state.AvailableSlots[index].slots[i.toString()];
const slots = Object.assign({}, this.state.AvailableSlots[index].slots, {[i.toString()]: !selected });
this.setState({
AvailableSlots: [
...this.state.AvailableSlots.slice(0, index),
Object.assign({}, this.state.AvailableSlots[index], { slots: slots, time: item.time }),
...this.state.AvailableSlots.slice(index + 1)
]
});
}
In your handleSlots function check , if slot is selected.
const handleSlots = (max_slot, item, index) => {
let slots = [];
for (let counter = 1; counter <= max_slot; counter++) {
slots.push(
<div
className="col"
onClick={() => this.handleClick(counter, item, index)}
style={{
margin: 5,
backgroundColor: item.slots[counter.toString()] ? 'green' : '#575756',
height: '28px'
}}
/>
);
}
// console.log(slots);
return slots;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script src="//unpkg.com/office-ui-fabric-react/dist/office-ui-fabric-react.js"></script>
<div id="root"></div>
<script type="text/babel">
class SlotBookingComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
bookingDate: '',
buttonColor: 'red',
startTime: '',
endTime: '',
AvailableSlots: [],
slots: null
};
this.updateState = this.updateState.bind(this);
//this.handleSlots = this.handleSlots.bind(this);
}
onButtonPress() {
this.setState({
buttonColor: '#123eee'
});
}
parseIn(date_time) {
var d = new Date();
d.setHours(date_time.substring(11, 13));
d.setMinutes(date_time.substring(14, 16));
return d;
}
getTimeSlots(time1, time2) {
var arr = [];
while (time1 < time2) {
arr.push({
time: time1.toTimeString().substring(0, 5),
slots: {
'1': false,
'2': false,
'3': false,
'4': false
}
});
time1.setMinutes(time1.getMinutes() + 30);
}
return arr;
}
updateState() {
var startTime = '2019-05-06T10:30:00+05:30';
var endTime = '2019-05-06T22:00:00+05:30';
startTime = this.parseIn(startTime);
endTime = this.parseIn(endTime);
var intervals = this.getTimeSlots(startTime, endTime);
this.setState({
AvailableSlots: intervals
});
}
componentDidMount() {
this.updateState();
}
componentWillReceiveProps(props) {
this.setState({ slots: props.SlotsData });
console.log(this.state.slots);
}
handleClick(i, item, index) {
const selected = this.state.AvailableSlots[index].slots[i.toString()];
const slots = Object.assign({}, this.state.AvailableSlots[index].slots, {
[i.toString()]: !selected
});
this.setState({
AvailableSlots: [
...this.state.AvailableSlots.slice(0, index),
Object.assign({}, this.state.AvailableSlots[index], {
slots: slots,
time: item.time
}),
...this.state.AvailableSlots.slice(index + 1)
]
});
}
render() {
const AvailableSlots = this.state.AvailableSlots;
const handleSlots = (max_slot, item, index) => {
let slots = [];
for (let counter = 1; counter <= max_slot; counter++) {
slots.push(
<div
className="col"
onClick={() => this.handleClick(counter, item, index)}
style={{
margin: 5,
backgroundColor: item.slots[counter.toString()]
? 'green'
: '#575756',
height: '28px'
}}
/>
);
}
// console.log(slots);
return slots;
};
const RowData = AvailableSlots.map(function(item, index) {
//max_slot wold come from API
var max_slot = 4;
if (max_slot == 1) {
return (
<div className="row">
<p>{item}</p>
<div
className="col"
key={item}
style={{ backgroundColor: '#123eee', height: '28px' }}
>
col
</div>
</div>
);
} else {
return (
<div className="data">
<div className="row test">
<div className="slot">{item.time}- </div>
{handleSlots(max_slot, item, index)}
</div>
</div>
);
}
});
return (
<div className="container">
<div className="col-md-9 slot-window product-details">{RowData}</div>
</div>
);
}
}
ReactDOM.render(<SlotBookingComponent />, document.getElementById("root"));
</script>
I use expanded rows in fixed-data-table-2 component. I have 3 levels on inner tables:
If I click collapse cells in inner table (second nested level), rows don't expand and last nested table is not rendered. It occurs after first click of parent row, but after second click the table is rendered.
What is more strange, this behaviour doesn't happen if
a) I click first three rows of second table or
b) if I expand first row in main(first) table
It happens with last rows of second table if other than first row of main table is expanded.
You can see this behaviour in this and this recording.
codesandbox
CollapseCell.jsx
import React from 'react';
import { Cell } from 'fixed-data-table-2';
const { StyleSheet, css } = require('aphrodite');
export default class CollapseCell extends React.PureComponent {
render() {
const {
data, rowIndex, columnKey, collapsedRows, callback, ...props
} = this.props;
return (
<Cell {...props} className={css(styles.collapseCell)}>
<a onClick={evt => callback(rowIndex)}>{collapsedRows.has(rowIndex) ? '\u25BC' : '\u25BA'}</a>
</Cell>
);
}
}
const styles = StyleSheet.create({
collapseCell: {
cursor: 'pointer',
},
});
TestMeet.jsx
import React, { Component } from 'react';
import debounce from 'lodash/debounce';
import { Table, Column, Cell } from 'fixed-data-table-2';
import isEmpty from 'lodash/isEmpty';
import 'fixed-data-table-2/dist/fixed-data-table.min.css';
import CollapseCell from './CollapseCell.jsx';
import SecondInnerTable from './SecondInnerTable';
const { StyleSheet, css } = require('aphrodite');
export default class TestMeetView extends Component {
static propTypes = {};
state = {
tableData: [
{
start: '5/19',
end: '5/20',
host: 'DACA',
},
{
start: '6/15',
end: '6/15',
host: 'DACA',
},
{
start: '6/16',
end: '6/17',
host: 'DACA',
},
{
start: '7/15',
end: '7/16',
host: 'DACA',
},
{
start: '7/30',
end: '7/31',
host: 'DACA',
},
],
columnWidths: {
start: 200,
end: 200,
host: 200,
action: 100,
},
tableContainerWidth: 0,
numOfExpRows: 0,
expChildRows: {},
collapsedRows: new Set(),
scrollToRow: null,
};
componentDidMount() {
this.updateWidth();
this.updateWidth = debounce(this.updateWidth, 200);
window.addEventListener('resize', this.updateWidth);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWidth);
}
onTableColumnResizeEndCallback = (newColumnWidth, columnKey) => {
this.setState(({ columnWidths }) => ({
columnWidths: {
...columnWidths,
[columnKey]: newColumnWidth,
},
}));
};
updateWidth = () => {
if (this.tableContainer.offsetWidth === this.state.tableContainerWidth) {
return;
}
if (
this.tableContainer &&
this.tableContainer.offsetWidth !== this.state.tableContainerWidth
) {
const newTableContainerWidth = this.tableContainer.offsetWidth;
this.setState({
tableContainerWidth: newTableContainerWidth,
columnWidths: {
start: newTableContainerWidth / 3,
end: newTableContainerWidth / 3,
host: newTableContainerWidth / 3,
},
});
}
};
handleCollapseClick = (rowIndex) => {
const { collapsedRows } = this.state;
const shallowCopyOfCollapsedRows = new Set([...collapsedRows]);
let scrollToRow = rowIndex;
if (shallowCopyOfCollapsedRows.has(rowIndex)) {
shallowCopyOfCollapsedRows.delete(rowIndex);
scrollToRow = null;
} else {
shallowCopyOfCollapsedRows.add(rowIndex);
}
let numOfExpRows = 0;
if (collapsedRows.size > 0) {
numOfExpRows = collapsedRows.size;
}
let resetExpChildRow = -1;
if (collapsedRows.has(rowIndex)) {
numOfExpRows -= 1;
resetExpChildRow = rowIndex;
} else {
numOfExpRows += 1;
}
if (resetExpChildRow === -1) {
this.setState({
numOfExpRows,
scrollToRow,
collapsedRows: shallowCopyOfCollapsedRows,
});
} else {
this.setState({
numOfExpRows,
scrollToRow,
collapsedRows: shallowCopyOfCollapsedRows,
expChildRows: {
...this.state.expChildRows,
[rowIndex]: 0,
},
});
}
};
subRowHeightGetter = (index) => {
const numExpChildRows = this.state.expChildRows[index] ? this.state.expChildRows[index] : 0;
return this.state.collapsedRows.has(index) ? 242 * (numExpChildRows + 1) + 50 : 0;
};
rowExpandedGetter = ({ rowIndex, width, height }) => {
if (!this.state.collapsedRows.has(rowIndex)) {
return null;
}
const style = {
height,
width: width - 10,
};
return (
<div style={style}>
<div className={css(styles.expandStyles)}>
<SecondInnerTable
changeNumOfExpandedRows={this.setNumOfInnerExpandedRows}
parentRowIndex={rowIndex}
/>
</div>
</div>
);
};
setNumOfInnerExpandedRows = (numOfExpandedRows, rowIndex) => {
this.setState({
expChildRows: {
...this.state.expChildRows,
[rowIndex]: numOfExpandedRows,
},
});
};
render() {
let sumOfExpChildRows = 0;
if (!isEmpty(this.state.expChildRows)) {
sumOfExpChildRows = Object.values(this.state.expChildRows).reduce((a, b) => a + b);
}
return (
<div className="test-view">
<div className="container-fluid">
<div className="mb-5" ref={el => (this.tableContainer = el)}>
<Table
scrollToRow={this.state.scrollToRow}
rowsCount={this.state.tableData.length}
rowHeight={40}
headerHeight={40}
width={this.state.tableContainerWidth}
height={(this.state.numOfExpRows + sumOfExpChildRows + 1) * 242}
subRowHeightGetter={this.subRowHeightGetter}
rowExpanded={this.rowExpandedGetter}
touchScrollEnabled
onColumnResizeEndCallback={this.onTableColumnResizeEndCallback}
isColumnResizing={false}
>
<Column
cell={<CollapseCell callback={this.handleCollapseClick} collapsedRows={this.state.collapsedRows} />}
fixed
width={30}
/>
<Column
columnKey="start"
header={<Cell>Start</Cell>}
cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex].start}</Cell>}
width={this.state.columnWidths.start}
isResizable
/>
<Column
columnKey="end"
header={<Cell>End</Cell>}
cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex].end}</Cell>}
width={this.state.columnWidths.end}
isResizable
/>
<Column
columnKey="host"
header={<Cell>Host</Cell>}
cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex].host}</Cell>}
width={this.state.columnWidths.host}
isResizable
/>
</Table>
</div>
</div>
</div>
);
}
}
const styles = StyleSheet.create({
expandStyles: {
height: '242px',
margin: '10px',
},
});
SecondInnerTable.jsx
import React, { Component } from 'react';
import { Table, Column, Cell } from 'fixed-data-table-2';
import debounce from 'lodash/debounce';
import 'fixed-data-table-2/dist/fixed-data-table.min.css';
import CollapseCell from './CollapseCell';
import ThirdInnerTable from './ThirdInnerTable';
const { StyleSheet, css } = require('aphrodite');
export default class SecondInnerTable extends Component {
state = {
tableData: [
{
dateOfSession: '5/19/18',
timeline: '4h00m/4h30m',
entries: '400/900',
},
{
dateOfSession: '5/19/18',
timeline: '4h00m/4h30m',
entries: '400/900',
},
{
dateOfSession: '5/19/18',
timeline: '4h00m/4h30m',
entries: '400/900',
},
{
dateOfSession: '5/19/18',
timeline: '4h00m/4h30m',
entries: '400/900',
},
{
dateOfSession: '5/19/18',
timeline: '4h00m/4h30m',
entries: '400/900',
},
],
tableColumns: {
dateOfSession: { label: 'Date of Session', width: 150 },
timeline: { label: 'Timeline', width: 150 },
entries: { label: 'Entries', width: 150 },
},
tableContainerWidth: 0,
tableContainerHeight: 252,
collapsedRows: new Set(),
scrollToRow: null,
};
componentDidMount() {
this.updateWidth();
this.updateWidth = debounce(this.updateWidth, 200);
window.addEventListener('resize', this.updateWidth);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWidth);
}
onSessionsTableColumnResizeEndCallback = (newColumnWidth, columnKey) => {
this.setState(({ tableColumns }) => ({
tableColumns: {
...tableColumns,
[columnKey]: { label: tableColumns[columnKey].label, width: newColumnWidth },
},
}));
};
updateWidth = () => {
if (this.tableContainer.offsetWidth === this.state.tableContainerWidth) {
return;
}
if (
this.tableContainer &&
this.tableContainer.offsetWidth !== this.state.tableContainerWidth
) {
const newTableContainerWidth = this.tableContainer.offsetWidth - 20;
const newColumnsWidth = newTableContainerWidth / 3;
this.setState(({ tableColumns }) => ({
tableContainerWidth: newTableContainerWidth,
tableColumns: {
dateOfSession: { label: tableColumns.dateOfSession.label, width: newColumnsWidth },
timeline: { label: tableColumns.timeline.label, width: newColumnsWidth },
entries: { label: tableColumns.entries.label, width: newColumnsWidth },
},
}));
}
};
handleCollapseClick = (rowIndex) => {
const { collapsedRows } = this.state;
const shallowCopyOfCollapsedRows = new Set([...collapsedRows]);
let scrollToRow = rowIndex;
if (shallowCopyOfCollapsedRows.has(rowIndex)) {
shallowCopyOfCollapsedRows.delete(rowIndex);
scrollToRow = null;
} else {
shallowCopyOfCollapsedRows.add(rowIndex);
}
let numOfExpRows = 0;
if (collapsedRows.size > 0) {
numOfExpRows = collapsedRows.size;
}
if (collapsedRows.has(rowIndex)) {
numOfExpRows -= 1;
} else {
numOfExpRows += 1;
}
this.setState(
{
tableContainerHeight: 252 * (numOfExpRows + 1),
scrollToRow,
collapsedRows: shallowCopyOfCollapsedRows,
},
() => {
this.props.changeNumOfExpandedRows(numOfExpRows, this.props.parentRowIndex);
},
);
};
subRowHeightGetter = index => (this.state.collapsedRows.has(index) ? 272 : 0);
rowExpandedGetter = ({ rowIndex, width, height }) => {
if (!this.state.collapsedRows.has(rowIndex)) {
return null;
}
const style = {
height,
width: width - 10,
};
return (
<div style={style}>
<div className={css(styles.expandStyles)}>
<ThirdInnerTable parentRowIndex={rowIndex} />
</div>
</div>
);
};
render() {
return (
<div className="mb-2" ref={el => (this.tableContainer = el)}>
<Table
scrollToRow={this.state.scrollToRow}
rowsCount={this.state.tableData.length}
rowHeight={40}
headerHeight={50}
width={this.state.tableContainerWidth}
height={this.state.tableContainerHeight}
subRowHeightGetter={this.subRowHeightGetter}
rowExpanded={this.rowExpandedGetter}
touchScrollEnabled
onColumnResizeEndCallback={this.onSessionsTableColumnResizeEndCallback}
isColumnResizing={false}
>
<Column
cell={<CollapseCell callback={this.handleCollapseClick} collapsedRows={this.state.collapsedRows} />}
fixed
width={30}
/>
{Object.keys(this.state.tableColumns).map(key => (
<Column
key={key}
columnKey={key}
header={<Cell>{this.state.tableColumns[key].label}</Cell>}
cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex][key]}</Cell>}
width={this.state.tableColumns[key].width}
isResizable
/>
))}
</Table>
</div>
);
}
}
const styles = StyleSheet.create({
expandStyles: {
height: '252px',
margin: '10px',
},
});
ThirdInnerTable.jsx
import React, { Component } from 'react';
import debounce from 'lodash/debounce';
import { Table, Column, Cell } from 'fixed-data-table-2';
import 'fixed-data-table-2/dist/fixed-data-table.min.css';
export default class ThirdInnerTable extends Component {
state = {
tableData: [
{
eventNumber: '1',
qualifyingTime: 'N/A',
selected: 'N/A',
},
{
eventNumber: '1',
qualifyingTime: 'N/A',
selected: 'N/A',
},
{
eventNumber: '1',
qualifyingTime: 'N/A',
selected: 'N/A',
},
{
eventNumber: '1',
qualifyingTime: 'N/A',
selected: 'N/A',
},
{
eventNumber: '1',
qualifyingTime: 'N/A',
selected: 'N/A',
},
],
tableColumns: {
eventNumber: { label: 'Event number', width: 150 },
qualifyingTime: { label: 'Qualifying time', width: 150 },
selected: { label: 'Selected?', width: 150 },
},
tableContainerWidth: 0,
numOfColumns: 3,
};
componentDidMount() {
this.updateWidth();
this.updateWidth = debounce(this.updateWidth, 200);
window.addEventListener('resize', this.updateWidth);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWidth);
}
onEventsTableColumnResizeEndCallback = (newColumnWidth, columnKey) => {
this.setState(({ tableColumns }) => ({
tableColumns: {
...tableColumns,
[columnKey]: { label: tableColumns[columnKey].label, width: newColumnWidth },
},
}));
};
updateWidth = () => {
if (this.tableContainer.offsetWidth === this.state.tableContainerWidth) {
return;
}
if (
this.tableContainer &&
this.tableContainer.offsetWidth !== this.state.tableContainerWidth
) {
const newTableContainerWidth = this.tableContainer.offsetWidth;
const columnWidth = newTableContainerWidth / 3;
this.setState(({ tableColumns }) => ({
tableContainerWidth: newTableContainerWidth,
tableColumns: {
eventNumber: { label: tableColumns.eventNumber.label, width: columnWidth },
qualifyingTime: { label: tableColumns.qualifyingTime.label, width: columnWidth },
selected: { label: tableColumns.selected.label, width: columnWidth },
},
}));
}
};
render() {
return (
<div className="mb-5" ref={el => (this.tableContainer = el)}>
<Table
rowsCount={this.state.tableData.length}
rowHeight={40}
headerHeight={50}
width={this.state.tableContainerWidth}
height={252}
touchScrollEnabled
onColumnResizeEndCallback={this.onEventsTableColumnResizeEndCallback}
isColumnResizing={false}
>
{Object.keys(this.state.tableColumns).slice(0, this.state.numOfColumns).map(key => (
<Column
key={key}
columnKey={key}
header={<Cell>{this.state.tableColumns[key].label}</Cell>}
cell={props => <Cell {...props}>{this.state.tableData[props.rowIndex][key]}</Cell>}
width={this.state.tableColumns[key].width}
isResizable
/>
))}
</Table>
</div>
);
}
}
I have updated the Sandbox code, Kindly check the below link, I think it is useful for you
Code Sandbox
Or
handleCollapseClick = rowIndex => {
const { collapsedRows } = this.state;
const shallowCopyOfCollapsedRows = new Set([...collapsedRows]);
let scrollToRow = rowIndex;
if (shallowCopyOfCollapsedRows.has(rowIndex)) {
shallowCopyOfCollapsedRows.delete(rowIndex);
scrollToRow = null;
} else {
shallowCopyOfCollapsedRows.add(rowIndex);
}
let numOfExpRows = 0;
if (collapsedRows.size > 0) {
numOfExpRows = collapsedRows.size;
}
let resetExpChildRow = -1;
if (collapsedRows.has(rowIndex)) {
numOfExpRows -= 1;
resetExpChildRow = rowIndex;
} else {
numOfExpRows += 1;
}
if (resetExpChildRow === -1) {
this.setState({
tableContainerHeight: 250 * numOfExpRows,
scrollToRow,
collapsedRows: shallowCopyOfCollapsedRows
});
} else {
this.setState({
numOfExpRows,
scrollToRow,
collapsedRows: shallowCopyOfCollapsedRows,
expChildRows: {
...this.state.expChildRows,
[rowIndex]: 0
}
});
}
};
I should have commented, but I don't have the required no. of points for that. Have you fixed the issue? Because I just opened the link you have attached and it's working fine
UPDATE
Your code works fine. The problem with your approach is that your click event is triggered on the arrow and not on your entire div (or cell). Therefore, you need to be on point and only click on the arrow in order for your row to expand. If you don't want this behavior, I'd suggest putting your click event on the div (or cell)
Here is a working video that demonstrates that it's working fine:
https://www.useloom.com/share/2c725f3fede942b09c661a765c72634e
I have taken a screenshot from your video which shows that because you're not clicking on the arrow, that's why your row is not expanding