Search code examples
reactjstypescriptdrag-and-dropstyled-components

No overload matches for this call, extra custom prop of styled component


No overload matches for this call

For props x, y in StyledContainer, it's throwing "No overload matches for this call".

Here is the main error throwing on build

import React from "react";
import styled, { css } from "styled-components";

interface Props {
  onDragStart?: () => void;
  onDragEnd?: () => void;
  children: React.ReactNode;
  isDragging: boolean;
  x?: number;
  y?: number;
  onDrag?: (position: { translateX: number; translateY: number }) => void;
}
interface State {
  originalX: number;
  originalY: number;
  lastTranslateX: number;
  lastTranslateY: number;
  translateX: number;
  translateY: number;
  isDragging: boolean;
}
export default class EventRecap extends React.Component<Props, State> {
  state = {
    isDragging: false,

    originalX: 0,
    originalY: 0,

    translateX: 0,
    translateY: 0,

    lastTranslateX: 0,
    lastTranslateY: 0,
  };

  componentWillUnmount() {
    window.removeEventListener("mousemove", this.handleMouseMove);
    window.removeEventListener("mouseup", this.handleMouseUp);
  }

  handleMouseDown = ({
    clientX,
    clientY,
  }: {
    clientX: number;
    clientY: number;
  }) => {
    window.addEventListener("mousemove", this.handleMouseMove);
    window.addEventListener("mouseup", this.handleMouseUp);

    if (this.props.onDragStart) {
      this.props.onDragStart();
    }

    this.setState({
      originalX: clientX,
      originalY: clientY,
      isDragging: true,
    });
  };

  handleMouseMove = ({
    clientX,
    clientY,
  }: {
    clientX: number;
    clientY: number;
  }) => {
    const { isDragging } = this.state;
    const { onDrag } = this.props;

    if (!isDragging) {
      return;
    }

    this.setState(
      (prevState) => ({
        translateX: clientX - prevState.originalX + prevState.lastTranslateX,
        translateY: clientY - prevState.originalY + prevState.lastTranslateY,
      }),
      () => {
        if (onDrag) {
          onDrag({
            translateX: this.state.translateX,
            translateY: this.state.translateY,
          });
        }
      },
    );
  };

  handleMouseUp = () => {
    window.removeEventListener("mousemove", this.handleMouseMove);
    window.removeEventListener("mouseup", this.handleMouseUp);

    this.setState(
      {
        originalX: 0,
        originalY: 0,
        lastTranslateX: this.state.translateX,
        lastTranslateY: this.state.translateY,

        isDragging: false,
      },
      () => {
        if (this.props.onDragEnd) {
          this.props.onDragEnd();
        }
      },
    );
  };

  render() {
    const { children } = this.props;
    const { translateX, translateY, isDragging } = this.state;
    return (
      <StyledContainer
        x={translateX}
        y={translateY}
        isDragging={isDragging}
        onMouseDown={this.handleMouseDown}
      >
       {children}
  
      </StyledContainer>
    );
  }
}

const StyledContainer = styled.div.attrs<Props>((props) => ({
  style: { transform: `translate(${props.x}px, ${props.y}px)` },
}))`
  cursor: grab;
  ${({ isDragging }: { isDragging: boolean }) =>
    isDragging &&
    css`
      opacity: 0.8;
      cursor: grabbing;
    `};
`;

Solution

  • Instead of using attributes merging with styled.div.attrs<Props>, directly use custom props so that they are exposed as the styled component props definition as well:

    // Add the custom Props as a TypeScript type argument to styled.div
    // https://styled-components.com/docs/api#using-custom-props
    const StyledContainer2 = styled.div<Props>`
      cursor: grab;
      /* Directly adapt style based on props, instead of merging attributes
      https://styled-components.com/docs/basics#adapting-based-on-props */
      transform: ${(props) => css`translate(${props.x}px, ${props.y}px)`};
      ${({
        // No need to redefine type, it is directly taken from the custom props type argument of styled.div
        isDragging
      }) =>
        isDragging
          ? css`
              opacity: 0.8;
              cursor: grabbing;
            `
          : ""};
    `;
    

    Then you can use these props directly inside the style block and adapt style based on props (instead of the attributes merging).

    Another advantage is that the props typing is directly available when you interpolate your function, so there is no need to redefine the props type.

    Demo: https://codesandbox.io/s/laughing-rgb-oomc6n?file=/src/App.tsx:330-972