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>
);
}
}
Related
im prety new to React and im trying to use an autocomplete input. Im having problems getting the value from it and clearing the input values after submitting. Any help would be greatly appretiated.
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import "../AutoComplete/styles.css"
class Autocomplete extends Component {
static propTypes = {
suggestions: PropTypes.instanceOf(Array)
};
static defaultProps = {
suggestions: [],
};
constructor(props) {
super(props);
this.state = {
// The active selection's index
activeSuggestion: 0,
// The suggestions that match the user's input
filteredSuggestions: [],
// Whether or not the suggestion list is shown
showSuggestions: false,
// What the user has entered
userInput: this.props.value ? this.props.value : "",
};
}
//Order by 'code'
generateSortFn(prop, reverse) {
return function (a, b) {
if (a[prop] < b[prop]) return reverse ? -1 : 1;
if (a[prop] > b[prop]) return reverse ? 1 : -1;
return 0;
};
}
onChange = e => {
const { suggestions } = this.props;
const userInput = e.currentTarget.value;
// Filter our suggestions that don't contain the user's input
const filteredSuggestions = suggestions.sort(this.generateSortFn('code', true)).filter(
(suggestion, i) => {
let aux = suggestion.descrp+"- "+suggestion.code
return aux.toLowerCase().indexOf(userInput.toLowerCase()) > -1
}
);
this.setState({
activeSuggestion: 0,
filteredSuggestions,
showSuggestions: true,
userInput: e.currentTarget.value
});
};
onClick = e => {
this.setState({
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: e.currentTarget.innerText
});
};
onKeyDown = e => {
const { activeSuggestion, filteredSuggestions } = this.state;
// User pressed the enter key
if (e.keyCode === 13) {
this.setState({
activeSuggestion: 0,
showSuggestions: false,
userInput: filteredSuggestions[activeSuggestion].code+" - "+filteredSuggestions[activeSuggestion].descrp
});
}
// User pressed the up arrow
else if (e.keyCode === 38) {
if (activeSuggestion === 0) {
return;
}
this.setState({ activeSuggestion: activeSuggestion - 1 });
}
// User pressed the down arrow
else if (e.keyCode === 40) {
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
this.setState({ activeSuggestion: activeSuggestion + 1 });
}
};
render() {
const {
onChange,
onClick,
onKeyDown,
state: {
activeSuggestion,
filteredSuggestions,
showSuggestions,
userInput
}
} = this;
let suggestionsListComponent;
if (showSuggestions && userInput) {
if (filteredSuggestions.length) {
suggestionsListComponent = (
<ul className="suggestions">
{filteredSuggestions.map((suggestion, index) => {
let className="";
// Flag the active suggestion with a class
if (index === activeSuggestion) {
className = "suggestion-active";
}
return (
<li className={className} key={suggestion.code} onClick={onClick}>
{suggestion.code+" - "+suggestion.descrp}
</li>
);
})}
</ul>
);
} else {
suggestionsListComponent = (
<div className="no-suggestions">
<p>Sin sugerencias</p>
</div>
);
}
}
and the return (this is where i think im wrong)
return (
<Fragment>
<label htmlFor="autocomplete-input" className="autocompleteLabel">{this.props.label}</label>
<div className="centerInput">
<input
className="autocomplete-input"
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
defaultValue={this.props.initState}
value= {/* this.props.value ? this.props.value : */ userInput}
placeholder={this.props.placeholder}
selection={this.setState(this.props.selection)}
/>
{suggestionsListComponent}
</div>
</Fragment>
);
}
}
export default Autocomplete;
What I want is to use this component in different pages, so im passing the "selection" prop and setting the state there.
The input is working correctly (searches, gets the value and shows/hide the helper perfectly). The problem is i cant reset this inputs clearing them, and i suspect the error is in here.
I get the following warning (even with it somewhat functioning)
Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
This is the Component usage with useState:
<Autocomplete label='Out cost Center:' placeholder='Set the out cost center' suggestions={dataCostCenterHelper} selection={(text) => setOutCostCenter(text.userInput)} value={outCostCenter} />
and last this is how im tryin to clear the state that is set in "selection":
const clearData = async () => {
setOutCostCenter('-');
// other inputs with the same component
setOutVendor('-');
setOutRefNumber('-');
}
This gets called inside the function that handles the button submitting the form.
Thanks in advance!
Looking at the code you posted this line might be the problem:
selection={this.setState(this.props.selection)}
You are updating state directly inside the render method, this is not recommended.
Try using a selection prop or state field and update the prop inside a componenteDidMount life cycle
selection={this.state.selection}
I am trying to use the tagPicker from fluent ui. I am using as starting point the sample from the site:
https://developer.microsoft.com/en-us/fluentui#/controls/web/pickers
The problem is that the object I have has 3 props. the objects in the array are {Code:'string', Title:'string', Category:'string'}. I am using a state with a useeffect to get the data. SO far works fine, the problem is that the suggestion are rendered blank. It filter the items but does not show the prop I want.
Here is my code:
import * as React from 'react';
import {
TagPicker,
IBasePicker,
ITag,
IInputProps,
IBasePickerSuggestionsProps,
} from 'office-ui-fabric-react/lib/Pickers';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
const inputProps: IInputProps = {
onBlur: (ev: React.FocusEvent<HTMLInputElement>) => console.log('onBlur called'),
onFocus: (ev: React.FocusEvent<HTMLInputElement>) => console.log('onFocus called'),
'aria-label': 'Tag picker',
};
const pickerSuggestionsProps: IBasePickerSuggestionsProps = {
suggestionsHeaderText: 'Suggested tags',
noResultsFoundText: 'No color tags found',
};
const url="url_data"
export const TestPicker: React.FunctionComponent = () => {
const getTextFromItem = (item) => item.Code;
const [state, setStateObj] = React.useState({items:[],isLoading:true})
// All pickers extend from BasePicker specifying the item type.
React.useEffect(()=>{
if (!state.isLoading) {
return
} else {
caches.open('cache')
.then(async cache=> {
return cache.match(url);
})
.then(async data=>{
return await data.text()
})
.then(data=>{
const state = JSON.parse(data).data
setStateObj({items:state,isLoading:false})
})
}
},[state.isLoading])
const filterSuggestedTags = (filterText: string, tagList: ITag[]): ITag[] => {
return filterText
? state.items.filter(
tag => tag.Code.toLowerCase().indexOf(filterText.toLowerCase()) === 0 && !listContainsTagList(tag, tagList),
).slice(0,11) : [];
};
const listContainsTagList = (tag, state?) => {
if (!state.items || !state.items.length || state.items.length === 0) {
return false;
}
return state.items.some(compareTag => compareTag.key === tag.key);
};
return (
<div>
Filter items in suggestions: This picker will filter added items from the search suggestions.
<TagPicker
removeButtonAriaLabel="Remove"
onResolveSuggestions={filterSuggestedTags}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
itemLimit={1}
inputProps={inputProps}
/>
</div>
);
};
I just got it, I need to map the items to follow the {key, name} from the sample. Now it works.
setStateObj({items:state.map(item => ({ key: item, name: item.Code })),isLoading:false})
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 "";
}
};
I am working with ReactJS and using Semantic UI.
I have a breadcrumb style navigation menu on my page built using the 'Breadcrumb' and 'Dropdown' components from Semantic UI.
I am trying to make the behaviour of the menu accessible by allowing the user to use the keyboard (tab, arrows and enter) to navigate the menu, the dropdown options and to make a selection from the dropdown options.
The issue I am trying to solve is when using the keyboard, the user should only be able to select an option when it is focused on (using the arrow keys) followed by pressing the 'enter' key. At the moment when the user focuses on an option, it gets selected when they 'tab' to the next element.
Is it possible to change this behaviour so an option is only selected when the enter key is pressed?
I tried implementing "selectOnNavigation={false}" on the Dropdown component however this still results in the option being selected when 'tabbing' away from the element.
I also tried manipulating the event handler onBlur() but it still selected the option when tabbing.
Code for my component:
import React from 'react';
import { connect } from 'react-redux';
import { Breadcrumb, Dropdown } from 'semantic-ui-react';
import PropTypes from 'prop-types';
import {
saveBreadcrumbOptions,
changeBreadcrumbMarket,
changeBreadcrumbParentGroup,
changeBreadcrumbAgency
} from '../../actions/breadcrumbActions';
import MenuFiltersAPI from '../../api/MenuFiltersAPI';
import './OMGBreadcrumb.css';
export class OMGBreadcrumb extends React.Component {
constructor(props) {
super(props);
this.state = {
markets: [],
parentGroups: [],
agencies: []
};
this.getBreadcrumbOptions = this.getBreadcrumbOptions.bind(this);
this.handleMarketChange = this.handleMarketChange.bind(this);
this.handleParentGroupChange = this.handleParentGroupChange.bind(this);
this.handleAgencyChange = this.handleAgencyChange.bind(this);
this.handleParentGroupBlur = this.handleParentGroupBlur.bind(this);
}
componentDidMount() {
this.getBreadcrumbOptions();
}
async getBreadcrumbOptions() {
// get all options
const breadcrumb = this.props.breadcrumb.options.length
? this.props.breadcrumb.options
: await MenuFiltersAPI.getBreadcrumb();
// if a market is selected, use it
// otherwise use the first one
let selectedMarket = breadcrumb.find(m => (
m.id === this.props.breadcrumb.selectedMarket
));
selectedMarket = selectedMarket
? selectedMarket.id
: breadcrumb[0].id;
this.props.saveBreadcrumbOptions(breadcrumb);
this.setState({ markets: breadcrumb }, () => this.changeMarket(selectedMarket));
}
changeMarket(id) {
// get parent group options for given market
const parentGroups = this.state.markets.find(market => market.id === id).parent_groups;
// if a parent group is selected, use it
// otherwise use the first one
let selectedParentGroup = parentGroups.find(pg => (
pg.id === this.props.breadcrumb.selectedParentGroup
));
selectedParentGroup = selectedParentGroup
? selectedParentGroup.id
: parentGroups[0].id;
this.props.changeBreadcrumbMarket(id);
this.setState({ parentGroups }, () => this.changeParentGroup(selectedParentGroup));
}
changeParentGroup(id) {
// get agency options for dropdown menu
const agencies = this.state.parentGroups.find(parentGroup => parentGroup.id === id).agencies;
let selectedAgency = agencies.find(a => (
a.id === this.props.breadcrumb.selectedAgency
));
selectedAgency = selectedAgency
? selectedAgency.id
: agencies[0].id;
this.props.changeBreadcrumbParentGroup(id);
this.setState({ agencies }, () => this.changeAgency(selectedAgency));
}
changeAgency(id) {
// const selectedAgency = agencyOptions[0].value
this.props.changeBreadcrumbAgency(id);
}
handleMarketChange(e, { value }) {
console.log(value)
this.changeMarket(value);
}
handleParentGroupChange(e, { value }) {
console.log(value)
// if(!!value){
// return;
// }
this.changeParentGroup(value);
}
handleAgencyChange(e, { value }) {
console.log(value)
this.changeAgency(value);
}
handleParentGroupBlur(e, {value}) {
e.preventDefault();
console.log(e.key)
if(e.key !== 'Enter'){
console.log('key was not enter')
return;
}
}
render() {
return (
<div id="OMGBreadcrumb">
<b>Show information by: </b>
<Breadcrumb>
<Breadcrumb.Section>
<Dropdown
selectOnNavigation={false}
options={this.state.markets.reduce((acc, cur) => {
acc.push({ text: cur.name, value: cur.id });
return acc;
}, [])}
value={this.props.breadcrumb.selectedMarket}
onChange={this.handleMarketChange}
openOnFocus={false}
/>
</Breadcrumb.Section>
<Breadcrumb.Divider icon='right chevron' />
<Breadcrumb.Section>
<Dropdown
selectOnNavigation={false}
options={this.state.parentGroups.reduce((acc, cur) => {
acc.push({ text: cur.name, value: cur.id });
return acc;
}, [])}
value={this.props.breadcrumb.selectedParentGroup}
onChange={this.handleParentGroupChange}
openOnFocus={false}
onBlur={this.handleParentGroupBlur}
/>
</Breadcrumb.Section>
<Breadcrumb.Divider icon='right chevron' />
<Breadcrumb.Section>
<Dropdown
// selectOnNavigation={false}
options={this.state.agencies.reduce((acc, cur) => {
acc.push({ text: cur.name, value: cur.id });
return acc;
}, [])}
value={this.props.breadcrumb.selectedAgency}
onChange={this.handleAgencyChange}
openOnFocus={false}
/>
</Breadcrumb.Section>
</Breadcrumb>
</div>
);
}
}
OMGBreadcrumb.propTypes = {
saveBreadcrumbOptions: PropTypes.func.isRequired,
changeBreadcrumbMarket: PropTypes.func.isRequired,
changeBreadcrumbParentGroup: PropTypes.func.isRequired,
changeBreadcrumbAgency: PropTypes.func.isRequired,
breadcrumb: PropTypes.objectOf(
PropTypes.oneOfType([
PropTypes.number,
PropTypes.array
])
).isRequired
};
export default connect(
store => ({
breadcrumb: store.breadcrumb
}),
{
saveBreadcrumbOptions,
changeBreadcrumbMarket,
changeBreadcrumbParentGroup,
changeBreadcrumbAgency
}
)(OMGBreadcrumb);
<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>
Appreciate any guidance.
You can add selectOnBlur={false} to your dropdown component and it will no longer select when the dropdown is blurred.
<Dropdown
...
selectOnBlur={false}
/>
https://codesandbox.io/s/semantic-ui-example-7qgcu?module=%2Fexample.js
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>