React Material UI v5 styled with defaultProps - reactjs

When using multiple styled components, the top one overrides other default props.
import { styled } from '#mui/material/styles'
import { Badge } from '#mui/material'
const Badge1 = styled(Badge)``
// this works if Badge1 is used directly: <Badge1 />
Badge1.defaultProps = {
max: Infinity
}
const Badge2 = styled(Badge1)`` // styled Badge1
// this overrides defaultProps from Badge1. Prop max: Infinity does no apply here
Badge2.defaultProps = {
variant: 'standard'
}
Badge2 has only variant: 'standard' default prop. It skips max: Infinity
How can I keep all the defaultProps from each level

When you style a component via multiple styled calls using Emotion, Emotion collapses the styling layers into a single wrapper component rather than adding an additional wrapper around the first wrapper. Emotion retains the defaultProps from the previous wrapper, but you are then overwriting that when you set Badge2.defaultProps.
You can retain any previous defaultProps with the following syntax:
Badge2.defaultProps = {
...Badge2.defaultProps,
variant: 'standard'
}
Below is an example demonstrating what happens with default props with each styled wrapping. The fix is demonstrated with StyledAgainWithDefaultRetainExisting.
import styled from "#emotion/styled";
function MyComponent({ className, ...defaults }) {
return <div className={className}>Defaults: {JSON.stringify(defaults)}</div>;
}
MyComponent.defaultProps = {
orig: true
};
const StyledMyComponent = styled(MyComponent)`
background-color: blue;
color: white;
`;
StyledMyComponent.defaultProps = {
styled: true
};
const StyledAgainNoDefaultsAdded = styled(StyledMyComponent)`
background-color: purple;
`;
const StyledAgainWithDefault = styled(StyledMyComponent)`
background-color: green;
`;
StyledAgainWithDefault.defaultProps = {
styledAgain: true
};
const StyledAgainWithDefaultRetainExisting = styled(StyledMyComponent)`
background-color: brown;
`;
StyledAgainWithDefaultRetainExisting.defaultProps = {
...StyledAgainWithDefaultRetainExisting.defaultProps,
styledAgainRetain: true
};
export default function App() {
return (
<div>
<MyComponent />
<StyledMyComponent />
<StyledAgainNoDefaultsAdded />
<StyledAgainWithDefault />
<StyledAgainWithDefaultRetainExisting />
</div>
);
}

Related

React Styled components, how to get props type of component wchich returns different elements

I decided to change the styling in the project from css modules to styled components.
I had a problem with one component, which, depending on the props, returns different elements - a button or a link.
here is code of it:
//type
export type ButtonProps = UIComponentProps &
React.ComponentPropsWithoutRef<"button"> & {
type?: "button";
};
export type AnchorProps = UIComponentProps &
React.ComponentPropsWithoutRef<"a"> & {
type: "link";
external?: boolean;
};
export type CustomButtonProps = ButtonProps | AnchorProps;
//component
import Link from "next/link";
import { CustomButtonProps } from "../Button";
const Button = (props: CustomButtonProps) => {
if (props.type === "link") {
// eslint-disable-next-line #typescript-eslint/no-unused-vars
const { type: _, external, ...anchorProps } = props;
const { children, disabled, href } = anchorProps;
if (disabled) {
return <button disabled={true}>{children}</button>;
} else {
return external ? (
<a {...anchorProps}>{children}</a>
) : (
<Link {...anchorProps} href={href || "#"}>
{children}
</Link>
);
}
}
const { children } = props;
return <button {...props}>{children}</button>;
};
export default Button;
When I used css modules, to change the styles, I just sent the className which became at the end of the list of classes and overlapped the previous styles
<Button className={styles.heroButton}>{HeaderText.heroButton}</Button>
// if pass 'type={"link"}' it become link el
Now i use styled components extend functionality
export const HeroButton = styled(Button)`
width: 60px;
height: 60px;
#media screen and (max-width: 425px) {
width: 50px;
height: 50px;
}
`;
If i extend a component that returns a single element all is working fine
But with Button multi component typescript not getting types
Is there a way to get types and not seperate multi component to two different?

How to set customized color for a Text component with styled-components?

I want to set a Text component's color dynamically. When there is a disable condition then change to another color.
import React from "react";
import styled, { css } from "styled-components";
type TextType = {
text: string;
disable: string;
};
export const Text: React.FC<TextType> = ({ text, disable }) => {
return <TextStyle disable>{text}</TextStyle>;
};
const TextStyle = styled.span`
${({ theme }) => css`
color: ${(props) =>
props.disable ? theme.color.text : theme.color.disable};
`}
`;
The props.disable got error
Property 'disable' does not exist on type 'ThemeProps<DefaultTheme>'. [2339]
The theme is
export const theme = {
color: {
text: black,
disable: blue,
},
};
I want to use it as
<Text disable text={text} />
Or
<Text text={text} disable={disable} />
Is it necessary to define a disable property in theme const?
i think it is because the first argument in your styled component Text is 'theme', while it should be props. so in your case there is a confusion between theme and props.
try this see if it works :
import React from "react";
import styled, { css } from "styled-components";
type TextType = {
text: string;
disable: string;
};
export const Text: React.FC<TextType> = ({ text, disable }) => {
return <TextStyle disable={disable} >{text}</TextStyle>;
};
const TextStyle = styled.span`
${(props) => css`
color: ${ props.disable ? props.theme.color.text : props.theme.color.disable};
`}
`;
Also, the disable prop that you are passing in your component is not the same as the disable property in your theme. If you do not wish to have a different name for each as to avoid confusion, may be you should at least say that the disable you are passing into your component as a prop is of type boolean, like so :
type TextType = {
text: string;
disable: boolean;
};

How to define props on framer-motion styled-component? (PSA)

PSA:
(not a question but I didn't see any answers to this on stack so here's the answer.)
Define props on a styled-components that wraps a motion, it's a little confusing how to define them when wrapping motion with styled().
how to define props according to styled components docs
import styled from 'styled-components';
import Header from './Header';
interface TitleProps {
readonly isActive: boolean;
}
const Title = styled.h1<TitleProps>`
color: ${(props) => (props.isActive ? props.theme.colors.main : props.theme.colors.secondary)};
`;
How to define the props in your code without motion:
import styled from "styled-components";
interface Props {
height?: number;
}
const Container = styled.div<Props>`
height: ${({ height }) => height};
`;
export default Container;
How to define the props in your code with motion:
import { HTMLMotionProps, motion } from "framer-motion";
import styled from "styled-components";
/**
* notice the props extend HTMLMotionProps<"div"> this is so all the default
* props are passed such as `onClick`
*/
interface Props extends HTMLMotionProps<"div"> {
height?: number;
}
//notice motion is called as a function instead of `motion.div`
const Container = styled(motion<Props>("div"))`
height: ${({ height }) => height};
`;
export default Container;
The problem is that you are defining your "motion div" wrong. It should be defined this way instead:
interface Props {
height?: number;
}
// motion is an object that gives you access to the html tags (like the div)
const Container = styled(motion.div)<Props>`
height: ${({ height }) => height};
`;
As you can see above, you just need to pass in motion.div as any other component, into the styled function.

TypeScript React, exporting styled component function

I am exporting a function to use as a mixin for styled components on React.
styles.ts
export const fontSize = (size: number) => `
font-size: ${(size / 16) * 100}%;
font-size: ${size / 16}rem;
`
button.tsx
import React, { FC } from 'react'
import styled from 'styled-components'
import { fontSize } from './styles'
type ButtonProps = {
onClick(): void
}
const StyledButton = styled.button`
background-color: #4f2e0e;
font-size: ${fontSize(18)};
cursor: pointer;
`
const Button: FC<ButtonProps> = (props) => (
<StyledButton>{props.children}</StyledButton>
)
export default Button
When I do, TypeScript warns:
#typescript-eslint/explicit-module-boundary-types: Missing return type on function.
I have tried to add allowExpressions: true to .eslintjs but is not working:
'#typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true}]

How to type a styled component without losing any prop with Typescript?

I'm new to styled components and I'd like to be able to type my styled components correctly so that when I pass props "vs code" I can autodetect all those props I have, not just the one in the theme or the ones I could put with an interface.
Would there be any way without using a HOC for it as I've seen in some other answer? Is it possible to get a general prop to use in all without having to be defining in each property of style this as in the example?
app.theme.ts
export const theme = {
palette: {
primaryColor: '#FF5018',
secondaryColor: '#252729',
tertiaryColor: '#1A1C1E',
textColor: 'white',
},
};
export type Theme = typeof theme;
navigation-bar.styled.component.ts
export const NavigationBarStyled = styled.div`
grid-area: navigation-bar-item;
padding-left: 2rem;
display: flex;
align-items: center;
color: ${({ theme }) => theme.palette.primaryColor};
background-color: ${({ theme }) => theme.palette.primaryColor};
`;
Thanks in advance,
Best
It could be solved as #Huy-Nguyen said but in practice, you lose properties on Styled Components or you have to define the same many times.
So the best option is this as the Styled-Components website says (to define a theme interface):
theme.ts
export default interface ThemeInterface {
primaryColor: string;
primaryColorInverted: string;
}
styled-components.ts
import * as styledComponents from "styled-components";
import ThemeInterface from "./theme";
const {
default: styled,
css,
createGlobalStyle,
keyframes,
ThemeProvider
} = styledComponents as styledComponents.ThemedStyledComponentsModule<ThemeInterface>;
export { css, createGlobalStyle, keyframes, ThemeProvider };
export default styled;
And then, you use that:
import styled from 'app/styled-components';
// theme is now fully typed
const Title = styled.h1`
color: ${props => props.theme.primaryColor};
`;
Just pass the link: https://www.styled-components.com/docs/api#define-a-theme-interface
Thank you so much for all.
For some reason (possibly due to the way styled-components's typescript definition is written), the typing for Theme works if you remove one of level of nesting. This snippet typechecks with no error for me (v4) i.e. typescript knows that primaryColor is a string:
const theme = {
primaryColor: '#FF5018',
secondaryColor: '#252729',
tertiaryColor: '#1A1C1E',
textColor: 'white',
};
type Theme = typeof theme;
type Props = Theme & {
// ... other keys
}
const NavigationBarStyled = styled.div<Props>`
grid-area: navigation-bar-item;
padding-left: 2rem;
display: flex;
align-items: center;
color: ${props => props.primaryColor};
background-color: ${props => props.primaryColor};
`;
We approached this is a different way.
NOTE: Styled Components v5 with React Native
Define your theme type.
// MyTheme.ts
export type MyTheme = {
colors: {
primary: string;
background: string;
};
};
Use the type on your themes.
// themes.ts
export const LightTheme: MyTheme = {
colors: {
primary: 'white',
background: 'white',
},
};
export const DarkTheme: MyTheme = {
colors: {
primary: 'grey',
background: 'black',
},
};
Use declaration merging to "merge" the MyTheme type into Styled Components default theme.
// styled.d.ts
import 'styled-components';
import { MyTheme } from '../src/themes/MyTheme';
declare module 'styled-components' {
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface DefaultTheme extends MyTheme {}
}
OK, cool. The theme prop is correctly typed.
What about the components themselves?
Wrap your specific component props in the StyledProps type.
import { StyledProps } from 'styled-components';
import styled from 'styled-components/native';
type MyViewProps = StyledProps<{
backgroundColor?: string;
isAlert?: boolean;
}>;
const MyView = styled.View(
(props: MyViewProps) => `
background-color: ${props.backgroundColor || props.theme.colors.background};
color: ${props.isAlert ? red : props.theme.colors.primary}
`,
);
In this example both props.backgroundColor and props.theme.colors.background will auto-complete. When you update MyTheme type or the specific component type it should just work. 👍
I don't know if it's the best method. But I found a solution as follows.
// theme.ts
const theme = {
colors: {
...
}
};
export type themeTypes = typeof theme;
export default theme;
// styled.d.ts ( in root folder )
import 'styled-components'
import { themeTypes } from '#/styles/theme'
declare module 'styled-components' {
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface DefaultTheme extends themeTypes {}
}

Resources