Home > Enterprise >  Simple react-dnd list in typescript is giving me compilation errors when trying to run the example
Simple react-dnd list in typescript is giving me compilation errors when trying to run the example

Time:11-29

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>
    );
  }

}

Reference: https://github.com/abeaudoin2013/react-beautiful-dnd-multi-list-typescript-example/blob/master/src/App.tsx

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.

  • Related