I'm making my own dropzone and I want to do an action while specific file extension dragging, I found that onDragEnter can't access file types, onDrop only can make that.
But I found a library that makes what I want to do, I tried hard to know from the source code how they do it but I fail. here is the link of the code
https://react-dropzone.netlify.com/#!/Accepting%20specific%20file%20types/5
Actually the file type is available, I don't know how react dropzone does it, but you can access the type via DragEvent.dataTransfer.items[0].type, items is the array of items being dragged so you can access the others via items[1] etc.
$('div').on('dragover',function(e){
console.log(e.originalEvent.dataTransfer.items[0].type);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>dropzone</div>
Well, you are right that the onDragEnter doesn't give you this information, however you can get the information from the onDragOver.
To do this with React, I guess the easiest is to use the ref callback to get the element where you want to drop to (or use the events on the element directly).
setTargetDropZone( element ) {
if (this.dropZone) {
// remove any previous attached event
this.dropZone.removeEventListener('dragover', this.onDragOver );
}
// update the local field with the new element
this.dropZone = element;
if (!this.dropZone) {
return;
}
// if it is not null attach the new event
this.dropZone.addEventListener( 'dragover', this.onDragOver );
}
Somewhere you would have to define which file types you want to accept, and then you have to handle the incoming data to filter it.
const { fileTypes = null } = this.props;
const { items } = e.dataTransfer;
let dropCount = items.length;
// if it is restricted, count how many actually match
if (fileTypes) {
// items is not an array, so you could use Array.from here
dropCount = Array.from( items ).filter( item => fileTypes.some( type => item.type.includes( type ) ) ).length;
}
So if you combine this code, in the end, you could end up with something like this
const { Component } = React;
const DropState = {
'All': '0',
'No': '1',
'Some': '2',
'0': 'All',
'1': 'No',
'2': 'Some'
};
class DropZone extends Component {
constructor() {
super();
this.state = {
dropState: DropState.All,
dropCount: 0
};
this.setTargetDropZone = this.setTargetDropZone.bind( this );
}
onDragOver = e => {
const { fileTypes = null } = this.props;
const { items } = e.dataTransfer;
let dropCount = items.length;
if (fileTypes) {
dropCount = Array.from( items ).filter( item => fileTypes.some( type => item.type.includes( type ) ) ).length;
}
this.setState( {
dropCount,
dropState: dropCount === items.length ? DropState.All : dropCount === 0 ? DropState.No : DropState.Some
} );
}
setTargetDropZone( element ) {
if (this.dropZone) {
this.dropZone.removeEventListener('dragover', this.onDragOver );
}
this.dropZone = element;
if (!this.dropZone) {
return;
}
this.dropZone.addEventListener( 'dragover', this.onDragOver );
}
render() {
const { dropState, dropCount } = this.state;
const { placeholder = 'Drop some files here' } = this.props;
return (
<div ref={this.setTargetDropZone} style={{border: 'solid #a0a0a0 1px' }}>
{ dropCount > 0 && <span>{ `${DropState[dropState]} will be accepted (${dropCount})` }</span> }
{ dropCount === 0 && placeholder }
</div>
);
}
}
const container = document.querySelector('#container');
ReactDOM.render( <DropZone fileTypes={ ['image/jpg', 'image/jpeg', 'image/png' ] } />, container );
<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="container">
</div>
Related
I'm having a problem when I want to edit the dynamic select form.
The problem is that when I want to edit an existing option from the API, the new option doesn't appear
<GridBox>
{
// this code to lopping dinamic select from with dinamic option
productVariantSection && (
productVariantSection.map((variantItem, index) => {
const optionsVariant = []
variantItem.options && (
variantItem.options.map(datavalue => {
optionsVariant.push({
value: datavalue.uuid,
label: datavalue.name
})
})
)
// this code to get existing value
const valueSelect = []
productVariantDetail.data.data.product_variant_options.forEach(element => {
if (element.variant_name === variantItem.name) {
variantItem['optionSelected'] = element.uuid;
variantItem['optionEditSelected'] = { value: element.uuid, label: element.variant_option_name };
valueSelect.push(variantItem.optionEditSelected)
}
})
const valueSelectChanged = []
const handleSelectnEdited = (e) => {
variantItem['optionEditSelected'] = e
variantItem['optionSelected'] = e.value
valueSelectChanged.push(e)
// if i console log there is worked but on value still in existing value
console.log(valueSelectChanged);
}
return (
<GridItem key={variantItem.uuid}>
<label><Text type="body-3-bold">{variantItem.name}</Text></label>
<InputSelect
options={optionsVariant}
placeholder="Choose"
onChange={(e) => handleSelectnEdited(e)}
value={valueSelectChanged.length < 1 ? variantItem.optionEditSelected : valueSelectChanged}
/>
</GridItem>
)
})
)
}
</GridBox>
if i console log when handle change there is worked but on value still in existing value
Here i have three filters on selection of which i need to filter data in a table.
I am using if else statement to check and filter the data , hence i want to modify the code in some modular way to achieve the same can any one suggest me , should i go with switch case ?
if (mapFilter === 'Mapped') {
if (listFilter) {
const result = fullData.filter(
data =>
data.partner_mapping_classification.length > 0 &&
data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification.length > 0,
);
setFinalData(result);
}
} else if (mapFilter === 'Not Mapped') {
if (listFilter) {
const result = fullData.filter(
data =>
data.partner_mapping_classification === '' &&
data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification === '',
);
setFinalData(result);
}
} else if (mapFilter === 'All') {
if (listFilter) {
const result = fullData.filter(
data => data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification.length > 0,
);
setFinalData(result);
}
} else if (mapFilter === '' && listFilter !== '') {
const result = fullData.filter(
data => data.account === listFilter,
);
setFinalData(result);
} else if (mapFilter === '' && listFilter === '') {
setFinalData([]);
} else {
setFinalData([]);
}
};
Easy to scale method (followed by live-demo)
Using switch statements or multiple chained if( statements (or, even, multiple conditions within same if( statement) doesn't seem to be a good idea, as scaling and maintaining such code will become way too difficult.
As the opposite to above mentioned hardcoding techniques, I would suggest to have an object within your table component's state that will bind object properties (you wish your table entries to get filtered by) to keywords (attached to your inputs).
Assuming (based on your screenshot) you use MaterialUI for styling your components, following example would demonstrate above approach:
const { useState } = React,
{ render } = ReactDOM,
{ Container, TextField, TableContainer, Table, TableHead, TableBody, TableRow, TableCell } = MaterialUI,
rootNode = document.getElementById('root')
const sampleData = [
{id: 0, name: 'apple', category: 'fruit', color: 'green'},
{id: 1, name: 'pear', category: 'fruit', color: 'green'},
{id: 2, name: 'banana', category: 'fruit', color: 'yellow'},
{id: 3, name: 'carrot', category: 'vegie', color: 'red'},
{id: 4, name: 'strawberry', category: 'berry', color: 'red'}
],
sampleColumns = [
{id: 0, property: 'name', columnLabel: 'Item Name'},
{id: 1, property: 'category', columnLabel: 'Category'},
{id: 2, property: 'color', columnLabel: 'Item Color'}
]
const MyFilter = ({filterProperties, onFilter}) => (
<Container>
{
filterProperties.map(({property,id}) => (
<TextField
key={id}
label={property}
name={property}
onKeyUp={onFilter}
/>
))
}
</Container>
)
const MyTable = ({tableData, tableColumns}) => (
<TableContainer>
<Table>
<TableHead>
<TableRow>
{
tableColumns.map(({id, columnLabel}) => (
<TableCell key={id}>
{columnLabel}
</TableCell>
))
}
</TableRow>
</TableHead>
<TableBody>
{
tableData.map(row => (
<TableRow key={row.id}>
{
tableColumns.map(({id, property}) => (
<TableCell key={id}>
{row[property]}
</TableCell>
))
}
</TableRow>
))
}
</TableBody>
</Table>
</TableContainer>
)
const App = () => {
const [state, setState] = useState({
data: sampleData,
columns: sampleColumns,
filterObj: sampleColumns.reduce((r,{property}) => (r[property]='', r), {})
}),
onFilterApply = ({target:{name,value}}) => {
const newFilterObj = {...state.filterObj, [name]: value}
setState({
...state,
filterObj: newFilterObj,
data: sampleData.filter(props =>
Object
.entries(newFilterObj)
.every(([key,val]) =>
!val.length ||
props[key].toLowerCase().includes(val.toLowerCase()))
)
})
}
return (
<Container>
<MyFilter
filterProperties={state.columns}
onFilter={onFilterApply}
/>
<MyTable
tableData={state.data}
tableColumns={state.columns}
/>
</Container>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script><div id="root"></div>
As Sudhanshu pointed out, you should create event listeners for all these select dropdowns and then update state based on that.
I created a small sample of how I would do it, but just be warned that this isn't tested and I just wrote it without actually running the code or anything. So it is buggy for sure in some regard.
const fullData = ['first', 'second', 'third'];
const BigFilter = () => {
const [activeFilters, setActiveFilters] = useState([]);
const [filteredValues, setFilteredValues] = useState([]);
const handleFilterChange = (event) => {
const { target } = event;
const isInFilter = activeFilters.some((element) => element.name === target.name);
if (!isInFilter) {
setActiveFilters((currentState) => {
return [...currentState, { name: target.name, value: target.value }];
});
} else {
setActiveFilters((currentState) => {
return [...currentState.filter((x) => x.name !== target.name), { name: target.name, value: target.value }];
});
}
};
useEffect(() => {
// Just set full data as filtered values if no filter is active
if (activeFilters.length === 0) {
setFilteredValues([...fullData]);
return;
};
let finalData = [...fullData];
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const listData = activeFilters.find((element) => (element.name = 'list'));
if (listData) {
// Do some filtering for first select/dropdown
const { value } = listData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const statusData = activeFilters.find((element) => (element.name = 'status'));
if (statusData) {
// Do some filtering for second select/dropdown
const { value } = statusData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const amountData = activeFilters.find((element) => (element.name = 'amount'));
if (amountData) {
// Do some filtering for third select/dropdown
const { value } = amountData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
setFilteredValues(finalData);
// You can go with multiple if statements to filter everything step by step
}, [activeFilters]);
return (
<>
<select name="list" onChange={handleFilterChange}>
<option>List Option 1</option>
</select>
<select name="status" onChange={handleFilterChange}>
<option>Status Option 1</option>
</select>
<select name="amount" onChange={handleFilterChange}>
<option>Amount Option 1</option>
</select>
<div>
{/* Render filtered values */}
{filteredValues.map((singleValue) => singleValue.name)}
</div>
</>
);
};
The basic idea here is that all your <select> elements react to the same event listener, making it easier to coordinate.
You got two basic arrays as state (activeFilters and filteredValues). When onChange handler is triggered, you check the name of the filter and check if that filter is already present in your activeFilters state. If it isn't, you add its name and value to that state. That's why I used name="some name" on each <select> in order to identify it somehow. In other case, if the filter is already present in that state, we remove it and just add its entry again with the new value. (This can probably be written way better, but it's just to give you an idea.)
Both of these cases set new state for active filters with setActiveFilter. Then we have the useEffect hook below which filters all the data based on active filters. As you can see it has that dependency array as a second argument and I added activeFilters variable to it so that every time activeFilters updates it will trigger all the logic in useEffect and it will change your filteredValues.
The logic in useEffect will go step by step and check if each filter is active and filter data for each of them if they are active step by step. If the first filter is active it will filter data that's needed and store it again in finalData and then it will go to the second if statement and if the filter for that is active it will perform another filter, but now on already filtered data. In the end, you should get data that passes through all active filters. I'm sure there's a better way of doing this, but it's a start.
Btw, usually I wouldn't do this
finalData = finalData.filter((x) => x.something > 0);
Re-assigning the same variable with filtered data from it, but I would say it's ok in this case since that finalData variable was created in that useEffect scope and it cannot be mutated from outside the scope. So it's easy to track what it is doing.
I'm sorry if this doesn't work, but it might guide you to your solution.
You can add a filter to the fullData array and provide the value of each of the dropdowns to the filter function
fullData.filter(element => {
return element.account == first && element.account == second && element.account == third;
});
You can also put in checks for the filters, like if the value is just '' then return false i.e return the whole array else return the filtered list
I am trying to avoid an onClick event that is on each particular row, whenever show/less is being clicked under one column's data. I have a grid that basically has select, and multi select features. Also there is show more/show less for one of the column. Whenever I click on Show More/Less, the row selection is triggered. I want to avoid this. Can someone tell what is wrong here. say If a have multiple instances of similar grids, I would want the row select functionality to that grid which has prop in it
Sandbox: https://codesandbox.io/s/react-table-row-table-alternate-single-row-working-5fr81
import * as React from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
export default class DataGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRows: []
};
}
rowClick = (state, rowInfo) => {
if (rowInfo) {
let selectedRows = new Set();
return {
onClick: e => {
e.stopPropagation();
if (e.ctrlKey) {
selectedRows = this.state.selectedRows;
rowInfo._index = rowInfo.index;
if (
selectedRows.filter(row => row._index === rowInfo._index)
.length === 0
)
selectedRows.push(rowInfo);
else
selectedRows = selectedRows.filter(
row => row._index !== rowInfo._index
);
this.setState({
selectedRows
});
} else {
selectedRows = [];
rowInfo._index = rowInfo.index;
selectedRows.push(rowInfo);
}
this.setState(
{
selectedRows
},
() => {
console.log("final values", this.state.selectedRows);
this.props.rowClicked(this.state.selectedRows);
}
);
},
style: {
background:
this.state.selectedRows.some(e => e._index === rowInfo.index) &&
"#9bdfff"
}
};
} else {
return "";
}
};
render() {
return (
<ReactTable
data={this.props.data}
columns={this.props.columns}
getTrProps={this.rowClick}
/>
);
}
}
On your ShowMore component, you can modify your onClick event handler to call event.stopPropagation(). This will stop further propagation of the current event in the capturing and bubbling phases, and allow you to click on Show More/Less without triggering row selection.
const toggleTruncate = (e) => {
e.stopPropagation();
setShowMore(!isShowMore);
}
React actually defines the synthetic event, which will be available on your click event handler, without the need to call document.addEventListener(). You may read more about event handling in React over here.
And answering your second part of the question, given that the rowClicked event is an optional props of the DataGrid component, you will need to manually check it on the DataGrid component to ensure that it will only be called if it is defined. This is how it can be done:
if (rowClicked) {
// do the rest
}
And this is how your rowClick method should look like. Do take note that I have destructured the props object on the second line. This will ensure that row selection logic will be carried out only if rowClicked is defined.
rowClick = (state, rowInfo) => {
const { rowClicked } = this.props;
if (rowInfo && rowClicked) {
let selectedRows = new Set();
return {
onClick: e => {
e.stopPropagation();
if (e.ctrlKey) {
selectedRows = this.state.selectedRows;
rowInfo._index = rowInfo.index;
if (
selectedRows.filter(row => row._index === rowInfo._index)
.length === 0
)
selectedRows.push(rowInfo);
else
selectedRows = selectedRows.filter(
row => row._index !== rowInfo._index
);
this.setState({
selectedRows
});
} else {
selectedRows = [];
rowInfo._index = rowInfo.index;
selectedRows.push(rowInfo);
}
this.setState(
{
selectedRows
},
() => {
rowClicked(this.state.selectedRows);
}
);
},
style: {
background:
this.state.selectedRows.some(e => e._index === rowInfo.index) &&
"#9bdfff"
}
};
} else {
return "";
}
};
This is my first question on StackOverflow. I want to build a little game with React, where users can drag and drop tetrominos onto a grid and also reposition or rotating them to their liking. The tetrominos are represented by a matrix and then every block gets rendered in a li element.
Example for the z-tetromino:
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0]
Unfortunately I cannot post images yet, that would make things easier.
The grid is too respresented by a matrix.
Now what I want to do is basically drag and drop these block matrices onto the grid, so that the values in the grid change accordingly (0 → 1 etc.).
The problem is, I have no clue how to drag multiple li elements at once with the standard HTML5 DnD API or with React DnD. When the user clicks on one li element of a certain tetromino, the whole piece should move. Maybe I could solve this using jQuery UI, but since in React no direct DOM manipulation is allowed, I'm left wondering how to do it.
I tried to drag one block onto the grid which worked semi optimally, because one block took the place of an entire row of grid blocks, even with display: inline-block set in CSS.
Here is some simple code from the first experiment.
onDragStart = e => {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text', e.target.id);
// e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
};
handleDrop = e => {
const pieceOrder = e.dataTransfer.getData('text');
// console.log(document.getElementById(pieceOrder));
// e.target.appendChild(document.getElementById(pieceOrder));
// console.log(pieceOrder);
e.target.replaceWith(document.getElementById(pieceOrder));
e.target.remove();
};
renderEmptyBoardCell(i) {
return (
<li key={i} className="emptyBoardCell" onDragOver={(e) => e.preventDefault()} onDrop={(e) => this.handleDrop(e)}>></li>
);
}
renderTemplateBoardCell(i) {
return (
<li key={i} className="templateBoardCell" onDragOver={(e) => e.preventDefault()} onDrop={(e) => this.handleDrop(e)}>></li>
);
}
renderEmptyCell(i) {
return (
<li key={i} className="emptyCell"></li>
);
}
renderFilledCell(piece_config, i) {
return (
<li key={i} id={i} className={`filledCell ${piece_config}`} draggable onDragStart={this.onDragStart}></li>
);
}
So the question is, would that be theoretically possible with React DnD or any other library? If yes, what would be the approximate solution to DnD multiple elements at once.
Thanks for your time!
In case anyone is looking for a solution in 2020. Here is my current solution with react-dnd and react hooks. You can try the live demo here.
Here is another simpler example, you can check out the codesandbox here.
You can only drag one item at a time using react-dnd. Either use a different library, or somehow group together the different pieces into one item first, and then drag and drop that one item.
I know its a bit late but have you looked into: panResponder. I am looking into multiple d'n'd elements and panResponder is the most likely fit
A nice choice for your need could be react-beautiful-dnd.
Try this out It will surely work in your case!
react-beautiful-dnd multi drag pattern
https://github.com/atlassian/react-beautiful-dnd/tree/master/stories/src/multi-drag
demo: https://react-beautiful-dnd.netlify.app/?path=/story/multi-drag--pattern
import React, { Component } from 'react';
import styled from '#emotion/styled';
import { DragDropContext } from 'react-beautiful-dnd';
import initial from './data';
import Column from './column';
import type { Result as ReorderResult } from './utils';
import { mutliDragAwareReorder, multiSelectTo as multiSelect } from './utils';
import type { DragStart, DropResult, DraggableLocation } from 'react-beautiful-dnd';
import type { Task, Id } from '../types';
import type { Entities } from './types';
const Container = styled.div`
display: flex;
user-select: none;
justify-content: center;
`;
type State = {
entities: Entities,
selectedTaskIds: Id[],
columnFlag: false,
// sad times
draggingTaskId: Id,
};
const getTasks = (entities: Entities, columnId: Id): Task[] =>
entities.columns[columnId].taskIds.map(
(taskId: Id): Task => entities.tasks[taskId],
);
export default class TaskApp extends Component<any, State> {
state: State = {
entities: initial,
selectedTaskIds: [],
draggingTaskId: '',
};
componentDidMount() {
window.addEventListener('click', this.onWindowClick);
window.addEventListener('keydown', this.onWindowKeyDown);
window.addEventListener('touchend', this.onWindowTouchEnd);
}
componentWillUnmount() {
window.removeEventListener('click', this.onWindowClick);
window.removeEventListener('keydown', this.onWindowKeyDown);
window.removeEventListener('touchend', this.onWindowTouchEnd);
}
onDragStart = (start: DragStart) => {
const id: string = start.draggableId;
const selected: Id = this.state.selectedTaskIds.find(
(taskId: Id): boolean => taskId === id,
);
// if dragging an item that is not selected - unselect all items
if (!selected) {
this.unselectAll();
}
this.setState({
draggingTaskId: start.draggableId,
});
};
onDragEnd = (result: DropResult) => {
const destination = result.destination;
const source = result.source;
const draggableId = result.draggableId;
const combine = result.combine;
console.log('combine',combine);
console.log('destination',destination);
console.log('source',source);
console.log('draggableId',draggableId);
// nothing to do
if (!destination || result.reason === 'CANCEL') {
this.setState({
draggingTaskId: '',
});
return;
}
const processed: ReorderResult = mutliDragAwareReorder({
entities: this.state.entities,
selectedTaskIds: this.state.selectedTaskIds,
source,
destination,
});
this.setState({
...processed,
draggingTaskId: null,
});
};
onWindowKeyDown = (event: KeyboardEvent) => {
if (event.defaultPrevented) {
return;
}
if (event.key === 'Escape') {
this.unselectAll();
}
};
onWindowClick = (event: KeyboardEvent) => {
if (event.defaultPrevented) {
return;
}
this.unselectAll();
};
onWindowTouchEnd = (event: TouchEvent) => {
if (event.defaultPrevented) {
return;
}
this.unselectAll();
};
toggleSelection = (taskId: Id) => {
const selectedTaskIds: Id[] = this.state.selectedTaskIds;
const wasSelected: boolean = selectedTaskIds.includes(taskId);
console.log('hwwo',this.state.entities.columns);
console.log('hwwo',this.state.entities.columns.done.taskIds);
// if there is change in entities - update the state
const newTaskIds: Id[] = (() => {
// Task was not previously selected
// now will be the only selected item
if (!wasSelected) {
return [taskId];
}
// Task was part of a selected group
// will now become the only selected item
if (selectedTaskIds.length > 1) {
return [taskId];
}
// task was previously selected but not in a group
// we will now clear the selection
return [];
})();
this.setState({
selectedTaskIds: newTaskIds,
});
};
toggleSelectionInGroup = (taskId: Id) => {
const selectedTaskIds: Id[] = this.state.selectedTaskIds;
const index: number = selectedTaskIds.indexOf(taskId);
// if not selected - add it to the selected items
if (index === -1) {
this.setState({
selectedTaskIds: [...selectedTaskIds, taskId],
});
return;
}
// it was previously selected and now needs to be removed from the group
const shallow: Id[] = [...selectedTaskIds];
shallow.splice(index, 1);
this.setState({
selectedTaskIds: shallow,
});
};
// This behaviour matches the MacOSX finder selection
multiSelectTo = (newTaskId: Id) => {
const updated: string[] | null | undefined = multiSelect(
this.state.entities,
this.state.selectedTaskIds,
newTaskId,
);
if (updated == null) {
return;
}
this.setState({
selectedTaskIds: updated,
});
};
unselect = () => {
this.unselectAll();
};
unselectAll = () => {
this.setState({
selectedTaskIds: [],
});
};
render() {
const entities = this.state.entities;
const selected = this.state.selectedTaskIds;
console.log('entities', entities);
console.log('selected', selected);
return (
<DragDropContext
onDragStart={this.onDragStart}
onDragEnd={this.onDragEnd}
>
<Container>
{entities.columnOrder.map((columnId: Id) => (
<Column
column={entities.columns[columnId]}
tasks={getTasks(entities, columnId)}
selectedTaskIds={selected}
key={columnId}
draggingTaskId={this.state.draggingTaskId}
toggleSelection={this.toggleSelection}
toggleSelectionInGroup={this.toggleSelectionInGroup}
multiSelectTo={this.multiSelectTo}
entities={entities}
/>
))}
</Container>
</DragDropContext>
);
}
}
I can't for the life of me figure out why I'm getting error:
Maximum call stack size exceeded
When this code is run. If I comment out:
const tabs = this.getTabs(breakpoints, panels, selectedTab);
the error goes away. I have even commented out other setState() calls to try and narrow down where the problem was at.
Code (removed the extra functions):
export default class SearchTabs extends Component {
constructor() {
super();
this.state = {
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
};
this.getTabs = this.getTabs.bind(this);
this.tabChanged = this.tabChanged.bind(this);
this.setSelectedFilter = this.setSelectedFilter.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
this.openDropdown = this.openDropdown.bind(this);
}
componentDidMount() {
const { panels } = this.props;
if (!panels || !panels.members || panels.members.length === 0) {
this.props.fetchSearch();
}
}
getTabs(breakpoints, panels, selectedTab) {
const tabs = panels.member.map((panel, idx) => {
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.setState({ filter: this.renderFilters(
panel,
breakpoints,
this.setSelectedFilter,
this.state.selectedFilter,
this.state.isDropdownOpen,
) || null });
return (
<TabItem
key={panelId}
classname={`${classname} search-tab`}
headline={headline}
idx={idx}
content={item}
onclick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
render() {
const { panels, selectedTab } = this.props;
if (!panels || panels.length === 0) return null;
const tabs = this.getTabs(breakpoints, panels, selectedTab);
return (
<div className={searchResultsTheme.filters}>
<ul className={`${searchResultsTheme.tabs} ft-search-tabs`}>{tabs}</ul>
<div className={searchResultsTheme.dropdown}>{this.state.filter}</div>
</div>
);
}
}
export const TabItem = ({ classname, content, onclick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onclick} >{content}</li>
);
Because of this loop:
render -----> getTabs -----> setState -----
^ |
| |
|____________________________________________v
You are calling getTabs method from render, and doing setState inside that, setState will trigger re-rendering, again getTabs ..... Infinite loop.
Remove setState from getTabs method, it will work.
Another issue is here:
onclick={this.tabChanged(idx, headline)}
We need to assign a function to onClick event, we don't need to call it, but here you are calling that method, use this:
onclick={() => this.tabChanged(idx, headline)}