I am trying to get this beautiful-react-dnd example working, and I am getting a few errors currently:
import * as React from 'react';
import {
DragDropContext,
Draggable,
Droppable,
DroppableProvided,
DraggableLocation,
DropResult,
DroppableStateSnapshot, DraggableProvided, DraggableStateSnapshot
} from 'react-beautiful-dnd';
import {Flex} from 'grid-styled'
import './App.css';
interface Item {
id: string;
content: string;
}
interface IAppState {
items: Item[];
selected: Item[];
}
interface IMoveResult {
droppable: Item[];
droppable2: Item[];
}
const getItems = (count: number, offset:number = 0): Item[] => {
return Array
.from({length: count}, (v, k) => k)
.map(k => ({
content: `item ${k offset}`,
id: `item-${k offset}`
}));
};
const reorder = (list: Item[], startIndex: number, endIndex: number):Item[] => {
const result = [...list];
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
/**
* Moves an item from one list to another list.
*/
const move = (source: Item[], destination: Item[], droppableSource:DraggableLocation, droppableDestination:DraggableLocation):IMoveResult | any => {
const sourceClone = [...source];
const destClone = [...destination];
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
const result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destClone;
return result;
};
const grid:number = 8;
const getItemStyle = (draggableStyle: any, isDragging: boolean):{} => ({
userSelect: 'none',
padding: 2*grid,
margin: `0 0 ${grid}px 0`,
background: isDragging ? 'lightgreen' : 'grey',
...draggableStyle
});
const getListStyle = (isDraggingOver: boolean):{} => ({
background: isDraggingOver ? 'lightblue' : 'lightgrey',
padding: grid,
width: 300,
minHeight: 400
});
export default class App extends React.Component<{}, IAppState> {
public id2List = {
droppable: 'items',
droppable2: 'selected'
};
constructor(props:any) {
super(props);
this.state = {
items: getItems(10, 0),
selected: getItems(5, 10)
};
this.onDragEnd = this.onDragEnd.bind(this);
this.getList = this.getList.bind(this);
}
public getList (id:string):Item[] {
return this.state[this.id2List[id]];
}
public onDragEnd(result: DropResult):void {
const { source, destination } = result;
if (!destination) {
return;
}
if (source.droppableId === destination.droppableId) {
const items = reorder(
this.getList(source.droppableId),
source.index,
destination.index
);
let state:IAppState = {...this.state};
if (source.droppableId === "droppable2") {
state = { ...this.state, selected: items };
} else if (source.droppableId === "droppable") {
state = {...this.state, items}
}
this.setState(state);
} else {
const resultFromMove:IMoveResult = move(
this.getList(source.droppableId),
this.getList(destination.droppableId),
source,
destination
);
this.setState({
items: resultFromMove.droppable,
selected: resultFromMove.droppable2
});
}
}
public render() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<Flex justifyContent={"space-between"}>
<Flex flexDirection="column">
<Droppable droppableId="droppable">
{(provided:DroppableProvided, snapshot:DroppableStateSnapshot) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
style={getListStyle(snapshot.isDraggingOver)}
>
{this.state.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(providedDraggable:DraggableProvided, snapshotDraggable:DraggableStateSnapshot) => (
<div>
<div
ref={providedDraggable.innerRef}
{...providedDraggable.draggableProps}
{...providedDraggable.dragHandleProps}
style={getItemStyle(
providedDraggable.draggableProps.style,
snapshotDraggable.isDragging
)}
>
{item.content}
</div>
{providedDraggable.placeholder}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</Flex>
<Droppable droppableId="droppable2">
{(providedDroppable2:DroppableProvided, snapshotDroppable2:DroppableStateSnapshot) => (
<div
ref={providedDroppable2.innerRef}
style={getListStyle(snapshotDroppable2.isDraggingOver)}>
{this.state.selected.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}>
{(providedDraggable2:DraggableProvided, snapshotDraggable2:DraggableStateSnapshot) => (
<div>
<div
ref={providedDraggable2.innerRef}
{...providedDraggable2.draggableProps}
{...providedDraggable2.dragHandleProps}
style={getItemStyle(
providedDraggable2.draggableProps.style,
snapshotDraggable2.isDragging
)}>
{item.content}
</div>
{providedDraggable2.placeholder}
</div>
)}
</Draggable>
))}
{providedDroppable2.placeholder}
</div>
)}
</Droppable>
</Flex>
</DragDropContext>
);
}
}
The are currently 2 errors that I am seeing when trying to run this example:
Error#1 Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'. TS7053
result[droppableSource.droppableId] = sourceClone;
Error#2 Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'Readonly'. TS7053
public getList(id: string): Item[] {
99 | return this.state[this.id2List[id]]; | ^ 100 | }
Here are my dependencies:
"dependencies": {
"@reduxjs/toolkit": "^1.5.1",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.7",
"@types/react-router-dom": "^5.3.2",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2",
"react-redux": "^7.2.0",
"react-router-dom": "^6.0.2",
"react-scripts": "4.0.3",
"typescript": "~4.1.5"
},
CodePudding user response:
If you use TypeScript, you have to provide types for your variables. Otherwise typechecking is not helping at all.
const result : { [key:string]:Item[]; } = {};
should take care of the first problem. I did not try the code, if it doesn't work I will correct it.
Same goes for this construct:
public id2List = {
droppable: 'items',
droppable2: 'selected'
}; // <-- what is the type here?
I guess it should be
public id2List = {
droppable: 'items',
droppable2: 'selected'
} as { [key:string]: keyof IAppState; };
To read more about these types, you can look at Index Signatures concept in TypeScript. Also, if you can avoid them and use proper attribute names in the type, it is better to do so, because narrowing the types makes your code less prone to errors.
Because the App component is a parametrized class, it gets IAppState
as a type of this.state
. To tie id2List
values to property names in IAppState
, a keyof operator is used.
It is worth mentioning, that you can also explicitly state 'any' as a type if you can't or don't want to figure out the proper typing. I.e. const result: any = {};
might still be acceptable for the compiler, depending on the settings. But it also defies any reason to use TS in the first place.