Home > Net >  Cannot read properties of undefined (reading '4')
Cannot read properties of undefined (reading '4')

Time:12-12

I know that there are a lot of questions similars to this one but I already tried each one of them and I still can't make it work.

Well, I'm building my own ui-library using typescript and styled-components. Honestly I'm pretty newbie on TS so there is a chance that I missing something obvious, so any advice will be appreciated.

Now going to what matters, here it is some code:

  /// THIS IS IN THE PROJECT I'M TRYING TO USE MY LIBRARY
  <Button 
        size={4}
      >
        My cool button
  </Button>

As you can see, I'm passing size prop to Button component of my own library. This prop is received by the library here:

const Button = (props: IButtonProps) => {
  return (
    <StyledButton {...props}>
     {props.leftIcon && <ButtonIcon marginRight={props.iconSpacing}>{props.leftIcon}</ButtonIcon>}
     {props.children}
     {props.rightIcon && <ButtonIcon marginLeft={props.iconSpacing}>{props.rightIcon}</ButtonIcon>}
    </StyledButton>
  )
}

And now as you can see I have the <StyledButton> using styled-components where I'm spreading the props:

const StyledButton = styled.button<IButtonProps>`
   // Other properties

  padding: ${props => props.theme.paddings[props.size as SizeTypes ?? 4]};

  // More properties

And the problem is exactly here. When I pass size={4} from the project using the library, I got the error in the title.

Of course I already defined a DeafultTheme with my objects and it's properties:

import 'styled-components'

declare module 'styled-components' {
  export interface DefaultTheme {

    // Some objects
   
    paddings: {
      1: string,
      2: string,
      3: string,
      4: string,
      5: string,
      6: string,
      7: string,
      8: string
    };

    // More objects
  }
}

And a theme consuming it:

import { DefaultTheme } from 'styled-components';

const theme: DefaultTheme = {
  paddings: {
    1: '2px',
    2: '4px',
    3: '6px',
    4: '8px',
    5: '10px',
    6: '12px',
    7: '16px',
    8: '24px',
  }
};

export { theme };

Finally, my IButtonProps interface:

interface IButtonProps extends React.ComponentPropsWithoutRef<'button'> {
  onClick?: (evt: React.FormEvent<HTMLElement>) => void;
  bg?: string;
  size: number;
  spacing: number;
  fontSize?: string;
  fontWeight?: string;
  borderRadius: number;
  color?: string;
  iconSpacing?: number;
  leftIcon?: React.ReactElement;
  rightIcon?: React.ReactElement;
  children?: React.ReactNode;
}

export { IButtonProps };

If there is something else that could give you more input in order to help me I will of course edit the post.

Thanks in advance!

CodePudding user response:

I just wrote a simple example which works well. Maybe you can find the differences between your and mine.

https://codesandbox.io/s/flamboyant-leaf-y4oyh?file=/src/App.js

import styled, { ThemeProvider } from "styled-components";

const paddingMap = {
  1: "10px",
  2: "20px",
  3: "30px",
  4: "40px"
};

const StyledButton = styled.button`
  font-size: ${(props) => props.theme.padding[props.size ?? 4]};
`;

const Button = (props) => {
  return <StyledButton {...props} />;
};

export default function App() {
  return (
    <ThemeProvider theme={{ padding: paddingMap }}>
      <div className="App">
        <Button>my default button</Button>
        <Button size={4}>my size 4 button</Button>
        <Button size={3}>my size 3 button</Button>
        <Button size={2}>my size 2 button</Button>
        <Button size={1}>my size 1 button</Button>
      </div>
    </ThemeProvider>
  );
}

CodePudding user response:

As I said in the comments, I was making an obvious mistake: I forgot to embebe my components with theme object.

So I made a new ThemeWrapper.tsx component with a ThemeProvider provider that passes the theme to each children prop (the components of the library):

import * as React from 'react';
import { ThemeProvider } from 'styled-components'
import { theme } from './theme';

const ThemeWrapper: React.FC = ({ children }) => {
  return (
    <ThemeProvider theme={theme}>
      {children}
    </ThemeProvider>
  )
};

export { ThemeWrapper };

And in my Button.tsx component:

const Button = (props: IButtonProps) => {
  return (
    <>
      <ThemeWrapper> --> Pay attention at this.
        <StyledButton {...props}>
          {props.leftIcon && <ButtonIcon mr={props.leftIconSpacing}>{props.leftIcon}</ButtonIcon>}
          {props.children}
          {props.rightIcon && <ButtonIcon ml={props.rightIconSpacing}>{props.rightIcon}</ButtonIcon>}
        </StyledButton>
      </ThemeWrapper>
    </>
  )
}

Thank you!

  • Related