How to extend styled component without passing props to underlying DOM element? - reactjs

I have a styled component that is extending a third-party component:
import Resizable from 're-resizable';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props.someProp}px;
`;
const PaneContainer = ({ children, someProp }) => (
<StyledPaneContainer
someProp={someProp}
>
{children}
</StyledPaneContainer>
);
export default PaneContainer;
This resulted in the following error in the browser console:
Warning: React does not recognize the someProp prop on a DOM
element. If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase someProp instead. If you
accidentally passed it from a parent component, remove it from the DOM
element
So, I changed the prop to have a data-* attribute name:
import Resizable from 're-resizable';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props['data-s']}px;
`;
const PaneContainer = ({ children, someProp }) => (
<StyledPaneContainer
data-s={someProp}
>
{children}
</StyledPaneContainer>
);
export default PaneContainer;
This works, but I was wondering if there was a native way to use props in the styled component without them being passed down to the DOM element (?)

2020 UPDATE: Use transient props
With the release 5.1.0 you can use transient props. This way you do not need an extra wrapper i.e. unnecessary code is reduced:
Transient props are a new pattern to pass props that are explicitly consumed only by styled components and are not meant to be passed down to deeper component layers. Here's how you use them:
const Comp = styled.div`
color: ${props => props.$fg || 'black'};
`;
render(<Comp $fg="red">I'm red!</Comp>);
Note the dollar sign ($) prefix on the prop; this marks it as transient and styled-components knows not to add it to the rendered DOM element or pass it further down the component hierarchy.
The new answer should be:
styled component:
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props.$someProp}px;
`;
parent component:
const PaneContainer = ({ children, someProp }) => (
<StyledPaneContainer
$someProp={someProp} // '$' signals the transient prop
>
{children}
</StyledPaneContainer>
);

As suggested by vdanchenkov on this styled-components github issue you can destructure the props and only pass the relevant ones to Resizable
const ResizableSC = styled(({ someProp, ...rest }) => <Resizable {...rest} />)`
flex: 0 0 ${(props) => props.someProp}px;
`;

For those (like me) that landed here because of an issue with SC and react-router's Link.
This is basically the same answer as #tskjetne, but with JS syntax style.
const StyledLink = styled(({ isCurrent, ...rest }) => <Link {...rest} />)(
({ isCurrent }) => ({
borderBottomColor: isCurrent ? 'green' : 'transparent',
}),
)

Starting with version 5.1, you can use shouldForwardProp configuration property:
This is a more dynamic, granular filtering mechanism than transient props. It's handy in situations where multiple higher-order components are being composed together and happen to share the same prop name. shouldForwardProp works much like the predicate callback of Array.filter. A prop that fails the test isn't passed down to underlying components, just like a transient prop.
Keep in mind that, as in this example, other chainable methods should always be executed after .withConfig.
I find it much cleaner than transient props, because you don't have to rename a property, and you become explicit about your intentions:
const ResizableSC = styled(Resizable).withConfig({
// Filter out the props to not be shown in DOM.
shouldForwardProp: (prop, defaultValidatorFn) =>
prop !== 'someProp'
&& defaultValidatorFn(prop),
})`
flex: 0 0 ${(props) => props.someProp}px;
`;
This is especially handy if you are using TypeScript and sharing the same props type both in your main component and the corresponding styled component:
import { HTMLAttributes } from 'react';
import styled from 'styled-components';
// Props type.
type CaptionProps = HTMLAttributes<HTMLParagraphElement> & {
size: number,
};
// Main component.
export const CaptionStyles = styled('p').withConfig<CaptionProps>({
// Filter out the props to not be shown in DOM.
shouldForwardProp: (prop, defaultValidatorFn) => (
prop !== 'size'
&& defaultValidatorFn(prop)
),
})`
flex: 0 0 ${(props) => props.size}px;
`;
// Corresponding styled component.
export function Caption({ size }: CaptionProps) {
return (
<CaptionStyles size={size} />
);
}
shouldForwardProp API reference

You can try with defaultProps:
import Resizable from 're-resizable';
import PropTypes from 'prop-types';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props.someProp}px;
`;
StyledPaneContainer.defaultProps = { someProp: 1 }
const PaneContainer = ({ children }) => (
<StyledPaneContainer>
{children}
</StyledPaneContainer>
);
export default PaneContainer;
We can also pass props using 'attrs'. This helps in attaching additional props (Example taken from styled components official doc):
const Input = styled.input.attrs({
// we can define static props
type: 'password',
// or we can define dynamic ones
margin: props => props.size || '1em',
padding: props => props.size || '1em'
})`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
/* here we use the dynamically computed props */
margin: ${props => props.margin};
padding: ${props => props.padding};
`;
render(
<div>
<Input placeholder="A small text input" size="1em" />
<br />
<Input placeholder="A bigger text input" size="2em" />
</div>
);

Related

Can you pass custom props to Material-UI v5 `styled()` components?

For example, in styled-components you can do the following:
const Div = styled.div`
background: ${props => props.primary ? "palevioletred" : "white"};
color: ${props => props.primary ? "white" : "palevioletred"};
`;
render(
<section>
<Div>Normal</Div>
<Div primary>Primary</Div>
</section>
);
Can you get the same result with Material-UI v5 styled utility without adding overrides to the global theme object like the example in the styled() docs?
Yes!
The most basic example of the above would look like this in MUI v5:
const Div = styled("div")(({ primary }) => ({
backgroundColor: primary ? "palevioletred" : "white",
color: primary ? "white" : "palevioletred"
}));
render (
<section>
<Div>Normal</Div>
<Div primary>Primary!</Div>
<section>
);
However, as the React docs say:
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
So MUI gave us the shouldForwardProp option to tell MUI whether it "should forward the prop" to the root node or not. The above example would look like this using that prop:
const Div = styled("div", {
shouldForwardProp: (prop) => prop !== "primary"
})(({ primary }) => ({
backgroundColor: primary ? "palevioletred" : "white",
color: primary ? "white" : "palevioletred"
}));
render (
<section>
<Div>Normal</Div>
<Div primary>Primary!</Div>
<section>
);
Explanation
The second argument to the styled function is an options object, one of the things it accepts is shouldForwardProp, which as the docs say, "Indicates whether the prop should be forwarded to the Component". So to remove the unknown prop warning from the console, we tell it not to pass our custom prop to the DOM element with shouldForwardProp: (prop) => prop !== "primary". Now we destructure this prop in the function call that returns our custom styles, and use it in those styles like we would any other function.
If you want to use the global theme styles here as well, just destructure it along with your custom prop(s), ie ({ primary, otherProp, thirdProp, theme }).
Working codesandbox.
MUI v5 styled API docs
Here is a fully-working MUI v5 TypeScript example where you can pass custom properties to a styled component:
import React from 'react';
import { Button, styled, Typography } from '#mui/material';
const PREFIX = 'NimbusButton';
const classes = {
root: `${PREFIX}-root`,
button: `${PREFIX}-button`
};
interface RootProps {
textColor?: 'primary' | 'secondary';
buttonTextColor?: 'primary' | 'secondary';
}
const Root = styled('div', {
shouldForwardProp: (prop) => prop !== 'textColor' && prop !== 'buttonTextColor',
name: 'MyThemeComponent',
slot: 'Root'
})<RootProps>(({ theme, textColor, buttonTextColor }) => ({
[`& .${classes.root}`]: {
color: textColor ? theme.palette.primary.main : theme.palette.secondary.main
},
[`& .${classes.button}`]: {
color: buttonTextColor ? theme.palette.primary.main : theme.palette.secondary.main
}
}));
type OwnProps = {
textColor: 'primary' | 'secondary';
buttonTextColor: 'primary' | 'secondary';
text?: string;
buttonText: string;
};
const CustomStyledButton: React.FC<OwnProps> = (props) => {
const { textColor, buttonTextColor, text, buttonText } = props;
return (
<Root className={classes.root} textColor={textColor} buttonTextColor={buttonTextColor}>
{text && <Typography variant={'body1'}>{text}</Typography>}
<Button className={classes.button}>{buttonText}</Button>
</Root>
);
};
export default CustomStyledButton;
If you are using TypeScript, I use utility function for it, so I always type prop names correctly:
export const shouldForwardProp = <CustomProps extends Record<string, unknown>>(
props: Array<keyof CustomProps>,
prop: PropertyKey,
): boolean => !props.includes(prop as string);
const MyComponent = styled('div', {
shouldForwardProp: (prop) => shouldForwardProp<MyComponentProps>(['isDisabled', 'bgColor'], prop),
})<MyComponentProps>(({ theme, isDisabled, size, bgColor }) => ({
...

Styling a Select element from react-select

I'm using a select element from the react-select library, and in my project I'm using styled-components.
I wanted to ask if it is possible for me to style it in my styles.ts file. if it's not possible, can you guys give any suggestions of how to do the styling??
Inside a React:FC
import Select from 'react-select'
...
const options = [
{ value: 'Income', label: 'Income' },
{ value: 'Expense', label: 'Expense' },
]
...
<Form>
<InputElement />
<Select options={options} />
</Form>
Yes, it is possible to provide your own custom styles, react-select provides an object-based approach for styling the Select component.
Reference to the docs
Here is a simple example,
const customStyles = {
option: (provided, state) => ({
...provided,
borderBottom: '1px dotted pink',
color: state.isSelected ? 'red' : 'blue',
padding: 20,
}),
control: () => ({
// none of react-select's styles are passed to <Control />
width: 200,
}),
singleValue: (provided, state) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = 'opacity 300ms';
return { ...provided, opacity, transition };
}
}
const App = () => (
<Select
styles={customStyles} // pass the customStyles object to the styles prop
options={...}
/>
);
Select is very customizable through the keys provided to the custom style object.
One thing to remember is that each key will be a style function where the first argument will be the provided styles and the second argument will the state of select i.e isFocused, isSelected.
EDIT- While it is the suggested way of providing custom styles with the object-based approach. If you really want to style the Select component with styled-components, here is an example (you have to provide a classNamePrefix through prop and style based on classNames)
More on details on styling with classNames docs
import Select from 'react-select';
import styled from 'styled-components';
const SelectElement = styled(Select)`
.react-select-container {
// custom styles
}
.react-select__control {
// custom styles
}
.react-select__indicators {
// custom styles
}
`;
const MySelect = (props) => {
return (
<SelectElement classNamePrefix="react-select" options={options} {...props}/>
)
}
Yes, you can do it like this
import ReactSelect from 'react-select';
import styled from 'styled-components';
const ReactSelectStyled = styled(ReactSelect)`
// you have to provide custom styles through class names
// example
.react-select__option {
// custom styles
}
`;
Additionally, you can change the prefix of the class name (i.e "react-select" part in the class names) by providing the prefix through the classNamePrefix prop to the Select component.

how to change image src using props with styled component and react

I want to reuse my component for multiple pages.
so, I have to change img src in styled.img component as passing props.
is it possible to change image src on styled component ?
I applied my code like below.
it's not working..
//logic
import Myimg from './img/icon_my';
const OngoingLists = ({ ...props }) => {
return (
<ListsOutWrap>
<ProgramListWrap {...props}>
<IconWrap {...props} >
<MyIcon {...props} />
</IconWrap>
</<ProgramListWrap>
</ListsOutWrap>
);
}
// styled component
const MyIcon = styled.img.attrs({
src: props => props.Img || { Myimg },
})`
width: 100%;
`;
I tried like above, any image doesn't show...even default img file doesn't show..
how can I change src file by using props ??
should I use background-image ?
thanks for ur help in advance ! :)
Styled components are still just react components, and you can simply provide a default prop value in the case a src prop isn't passed when used.
import Myimg from './img/icon_my';
const MyIcon = styled.img`
width: 100%;
`;
MyIcon.defaultProps = {
src: Myimg,
};
Usage:
<MyIcon /> // uses default Myimg
<MyIcon src="/path/to/sourceImage" /> // uses "/path/to/sourceImage"
BTW Correct attrs format to accept props would be to define a function taking props and returning an object:
const MyIcon = styled.img.attrs(props => ({
src: props.Img || Myimg,
}))`
width: 100px;
`;

Avoid passing down props using Styled Components and Typescript

I have the following Styled Component in a React app that works as expected:
const BaseButton = styled(Button)<{ borderColor: string }>`
border-color: ${({ borderColor }): string => borderColor};
`;
However it generates this warning message in the console:
React does not recognize the borderColor prop on a DOM element. If
you intentionally want it to appear in the DOM as a custom attribute,
spell it as lowercase bordercolor instead. If you accidentally
passed it from a parent component, remove it from the DOM element.
In order to avoid this, I've tried to implement the solution proposed in the documentation
Documentation example:
import styled from 'styled-components'
import Header, { Props as HeaderProps } from './Header'
const Title =
styled <
{ isActive: boolean } >
(({ isActive, ...rest }) => <Header {...rest} />)`
color: ${props => (
props.isActive ? props.theme.primaryColor : props.theme.secondaryColor
)}
`
My original code rewritten following the example:
const BaseButton = styled <
{ borderColor: string } >
(({ borderColor, ...rest }) => <Button {...rest} />)`
border-color: ${({ borderColor }): string => borderColor};
`;
But I get the following error message:
Parsing error: '>' expected
The error refers to <Button {rest...} />
This is my .babelrc config in case something is amiss:
{
"presets": ["#babel/env", "#babel/typescript", "#babel/preset-react"],
"plugins": [
"#babel/plugin-proposal-object-rest-spread",
"#babel/transform-runtime",
"#babel/plugin-transform-modules-commonjs"
]
}
Use Transient props
TL;DR just prefix your attriibute with $ sign. example $borderColor, $black, $any, $attribute.
If you want to prevent props meant to be consumed by styled components from being passed to the underlying React node or rendered to the DOM element, you can prefix the prop name with a dollar sign ($), turning it into a transient prop.
// typescript example
const BaseButton = styled(Button)<{ $borderColor: string }>`
border-color: ${({ $borderColor }): string => $borderColor};
`;
// js
const BaseButton = styled(Button)`
border-color: ${({$borderColor}) => $borderColor}
`;
// usage
<BaseButton $borderColor="red">Button</BaseButton>
2nd method
Checkout shouldForwardProp
const Comp = styled('div').withConfig({
shouldForwardProp: (prop, defaultValidatorFn) =>
!['hidden'].includes(prop)
&& defaultValidatorFn(prop),
}).attrs({ className: 'foo' })`
color: red;
&.foo {
text-decoration: underline;
}
`;
render(
<Comp hidden draggable="true">
Drag Me!
</Comp>
);
Your existing code was already right, but react gave you two options :
1) use lower case than snake-case
2) remove the attribute from DOM (you took this approach)
From the code I can see that you need the prop borderColor, but in the custom styling, you separated the props
({borderColor,... rest}) => <Button {...rest} />
You removed the border Color prop but you try to access in styled props the next line.
Instead try to rename the prop to bordercolor if you want warning to go away or just ignore warning.

How to use the styled-component property innerRef with a React stateless component?

I have the following styled component:
const Component = styled.div`
...
`;
const Button = (props) => {
return (
<Component>
...
</Component>
);
};
export default styled(Button)``;
I want to get a reference to the underlying div of Component. When I do the following I get null:
import Button from './Button.js';
class Foo extends React.Component {
getRef = () => {
console.log(this.btn);
}
render() {
return (
<Button innerRef={elem => this.btn = elem} />
);
}
}
Any ideas why I am getting null and any suggestions on how to access the underlying div?
Note: The reason I am doing this export default styled(Button)``; is so that the export styled component can be easily extended.
I managed to accomplish this by passing a function down as a prop to the styled-component that I was targeting, then passing the ref back as an argument of the function:
const Component = styled.div`
...
`;
const Button = (props) => {
return (
<Component innerRef={elem => props.getRef(elem)}>
...
</Component>
);
};
export default styled(Button)``;
...
import Button from './Button.js';
class Foo extends React.Component {
getRef = (ref) => {
console.log(ref);
}
render() {
return (
<Button getRef={this.getRef} />
);
}
}
Passing a ref prop to a styled component will give you an instance of the StyledComponent wrapper, but not to the underlying DOM node. This is due to how refs work. So it's not possible to call DOM methods, like focus, on styled components wrappers directly.
Thus to get a ref to the actual, wrapped inner DOM node, callback is passed to the innerRef prop as shown in example below to focus the 'input' element wrapped inside Styled component 'Input' on hover.
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: palevioletred;
background: papayawhip;
border: none;
border-radius: 3px;
`;
class Form extends React.Component {
render() {
return (
<Input
placeholder="Hover here..."
innerRef={x => { this.input = x }}
onMouseEnter={() => this.input.focus()}
/>
);
}
}
render(
<Form />
);
NOTE:-
String refs not supported (i.e. innerRef="node"), since they're already deprecated in React.

Resources