When using styled-components to style a custom functional react component, the styles are not being applied. Here is a simple example where the styles are not being applied to the StyledDiv:
const Div = () => (<div>test</div>)
const StyledDiv = styled(Div)`
color: red;
`;
What is the best way to make sure that the styles get applied correctly?
From the docs:
The styled method works perfectly on all of your own or any
third-party components as well, as long as they pass the className
prop to their rendered sub-components, which should pass it too, and
so on. Ultimately, the className must be passed down the line to an
actual DOM node for the styling to take any effect.
For example, your component would become:
const Div = ({ className }) => (<div className={className}>test</div>)
const StyledDiv = styled(Div)`
color: green;
`;
Modified example:
const styled = styled.default
const Div = ({ className }) => (<div className={className}>test</div>)
const StyledDiv = styled(Div)`
color: green;
font-size: larger;
`;
const App = () => {
return(<StyledDiv>Test</StyledDiv>)
}
ReactDOM.render(<App />, document.querySelector('.app'))
<script src="//unpkg.com/react#16.5.2/umd/react.development.js"></script>
<script src="//unpkg.com/react-dom#16.5.2/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/styled-components/3.4.9/styled-components.min.js"></script>
<div class="app"></div>
Using styled(Component) like that creates a class which is passed as a prop called className to the wrapped component.
You can then apply that to the root element:
const Div = ({ className }) => (
<div className={className}>test</div>
)
const StyledDiv = styled(Div)`
color: red;
`;
In case you cannot change the original component (it's imported or generated), let's assume that component is a <span>, you may wrap it with e.g. a <div> and nest the css rules, like so:
const ComponentICantTouchWrapper = ({className}) => (
<div className={className}><ComponentICantTouch /></div>
);
const StyledComponentICantTouch = styled(ComponentICantTouchWrapper)`
> span {
color: red;
}
`;
In my case, I was trying to use styled components with material UI. The thought I had was to do this: (it worked in most cases)
const StyledTableRow = ({ className, children }) => (
<TableRow className={className}>{children}</TableRow>
);
Related
I have this component
const MyComponent = (props) => {
const classes = useStyles(props);
return (
<div
className={classes.divBackground}
backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
export default MyComponent;
I am trying to pass backgroundImage link in props and trying to put into makeStyles
export default makeStyles(props => ({
divBackground:{
background:`url("${props.backgroundImageLink}")`,
}
}));
But this does not works
& I am getting this warning in console
index.js:1 Warning: React does not recognize the `backgroundImage` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `backgroundimage` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
You're not supposed to pass arbitrary attributes to the native elements (div in this case) because it doesn't do anything. The prop only works when passed in useStyles:
export default makeStyles({
divBackground: {
background: props => `url("${props.product?.image}")`,
}
});
Usage
const MyComponent = (props) => {
// you only need to pass the props here. useStyles will then use the
// prop.product.image to create the background property, generate a
// stylesheet and return the class name for you.
const classes = useStyles(props);
return (
<div
className={classes.divBackground}
// remove this line -----> backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
const MyComponent = (props) => {
const classes = useStyles(props)();
return (
<div
className={classes.divBackground}
backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
export default MyComponent;
then :
export default useStyles=(props)=>makeStyles(()=> ({
divBackground:{
background:`url("${props.backgroundImageLink}")`,
}
}));
Here are the docs on forwardedAs prop: https://styled-components.com/docs/api#forwardedas-prop.
As you can see, it's not very detailed and does not show how to properly use this prop.
My question is: How can I access the props that are sent down via forwardedAs? How can I define types for these forwardedAs props?
I'm able to access the forwardedAs props via ...rest parameters, but I need to define types for these props since I am also using styled-components with Typescript.
Here's my code example:
// Button.jsx
const myPropsToForward = {
href: 'https://somewebsite.com',
// ...more props
}
const Button = styled.button`
// ...button styles
`
const myComponent = () => (
<Button
as={Link}
to={ctaLink}
forwardedAs={myPropsToForward}
/>
)
// Link.jsx
const Link = ({
forwardedAs,
...rest
}) => {
// How do I access the forwardedAs prop from <Button /> here?
return (
<a href={forwardAs?.href} {...rest} />
)
}
Here, I need to be able to access the props within the Link component sent down via forwardedAs prop, but there's no documentation on how to do that. If I can access the forwardedAs prop, I can then define proper types for the Link component. I do not want to rely on the ...rest parameter as I cannot define types for that.
Thank you in advance.
forwardedAs
The forwardedAs prop is not for passing down props. It's actually for passing down an as prop to the next item in the chain. Consider this example:
const Button = styled.button`
padding: 20px;
`;
const Link = (props: any) => { // not properly typed
return <Button {...props} />;
};
const MyLink = styled(Link)`
background-color: blue;
`
const MyComponent = () => (
<MyLink forwardedAs={"div"}>
Text
</MyLink>
);
We have a Button which is a styled component and we have a MyLink which is another styled component that passes its props down to the Button. If we want to set the as prop on the Button, we can set forwardedAs on MyLink.
With <MyLink forwardedAs={"div"}>, the element that we ultimately render to the DOM is a div rather than a button and it applies the styles from both styled HOCs.
Passing Props
Based on your example here, the Link component is not actually needed. You can set as="a" on your Button to render it as a link and pass through myPropsToForward directly.
const myPropsToForward = {
href: "https://somewebsite.com"
// ...more props
};
const Button = styled.button`
background: yellow;
padding: 20px;
`;
const MyComponent = () => (
<Button as="a" {...myPropsToForward}>
Text
</Button>
);
I have a button with the following props - variant, loading, and disabled. Plus, I have a button group that accepts buttons as children and gaps them with 20px. Something like this:
Technically speaking, I have two components here. A <Button /> and a <ButtonGroup />. This would be achievable by writing:
const Button = styled.button`
// css implementation
:disabled {
opacity: 0.5;
}
`;
const ButtonGroup = styled.button`
// css implementation
${Button} + ${Button} {
margin-inline-start: 20px;
// PS - I'm aware I could use the `gap` property, but I'm not specifically talking about this example, but in general.
}
`;
// Usage
<ButtonGroup>
<Button ... />
<Button ... />
</ButtonGroup>
The last thing and the main issue here is to implement the loading state of the button. Or in general, adding extra logic to the styled component. So the "best" way I know of is to create a new functional component and then wrap it inside another styled. Something like this:
// Button.tsx
const StyledButton = styled.buton`...`;
const Button = (props) => {
return (
<StyledButton className={props.className}>
{props.loading && <LoadingSpinner />}
{props.children}
</StyledButton>
);
}
export default styled(Button)``; // It's needed for for nested styling.
...
// ButtonGroup.tsx
const ButtonGroup = styled.button`
// css implementation
${Button} + ${Button} {
margin-inline-start: 20px;
// PS - I'm aware I could use the `gap` property, but I'm not specifically talking about this example, but in general.
}
`;
It will work, of course, but I'm not sure if it's the best way. Currently, as you can see, I did it by calling styled component -> function component -> styled component for the simplest component. I'm not sure how it will scale with my other components, especially naming these components.
So my question is, is there a better, cleaner, simpler way of doing this?
I don't see a reason for three components, a pattern that works for me is using dot notation:
const StyledButton = styled.button``;
const Button = (props) => {
return (
<StyledButton className={props.className}>
{props.loading && <LoadingSpinner />}
{props.children}
</StyledButton>
);
};
Button.Styled = StyledButton;
export default Button;
In this way, you have a pattern where Component.Styled (if available) will always hold the runtime CSS-in-JS object which you can target.
Then in ButtonGroup implementation:
import { Button } from "#components";
// You can target the className
const ButtonGroup = styled.div`
${Button.Styled} { ... }
`;
// You can apply styles
styled(Button)
// You can use the component
<Button />
// Or extend style etc
<OtherButton as={Button.Styled} .../>
Here is my folder structure:
type/
- Heading.js
- index.js
card/
- Card.js
- CardGroup.js
- index.js
JSX:
<CardGroup>
<Heading>...</Heading>
<Card>...</Card<
</CardHeading>
Now I am trying to style the Heading and Card components differently if they are nested inside a CardGroup. CardGroup.js:
import Heading from '../type';
import Card from './Card';
const CardGroup = styled.div`
${Heading} { ... }
${Card} { ... }
`;
Works OK for the Heading but NOT for the Card. I came accross this issue before and I can't wrap my head around what's causing this. Is it because the are in the same folder? Is it because of the order they are imported in my app? Any ideas would be really helpful.
Updated:
My Card.js implementation:
const StyledCard = styled.div`...`;
const Card = props => {
...
<StyledCard {...props}>{props.children}</StyledCard>
}
You need to target the component generated by styled-component (StyledCard in your example).
// Card.js
const ContainerCard = styled.div`
width: 50px;
height: 50px;
`;
const Card = ({ className }) => {
return <ContainerCard className={className} />;
};
// Use any valid prop, Card.StyledComponent, Card.Style etc.
Card.className = ContainerCard;
export default Card;
// App.js
const Container = styled.div`
height: 100vh;
width: 100vw;
`;
// Styling custom components, through className prop.
const VioletredRedCard = styled(Card)`
background-color: palevioletred;
`;
// Target the component generated by styled-components
const CardWrapper = styled.div`
${Card.className} {
width: 100px;
height: 100px;
background-color: paleturquoise;
}
`;
const App = () => {
return (
<Container>
<CardWrapper>
<Card />
</CardWrapper>
<VioletredRedCard />
</Container>
);
};
And, of course, if you want to style the card via: styled(Card), be sure you pass the className prop like in the example above.
You can use classname attr with styled components.
const Card = styled.div.attrs({
className:"card"
})`
card styles
`
and you can use that className in CardGroup
const CardGroup=styled.div`
& .card{}
`
I have 3 components in 3 differents files :
//file1
const Icon1 = styled.div`
width: 10px;
`;
const Widget1 = () => <BaseWidget icon={<Icon1 />} />
//file2
const Icon2 = styled.div`
width: 15px;
`;
const Widget2 = () => <BaseWidget icon={<Icon2 />} />
//file3
const Icon3 = styled.div`
width: 20px;
`;
const Widget3 = () => <BaseWidget icon={<Icon3 />} />
and my base widget :
//basewidget
const BaseWidget = (props) => <div>{props.icon}</div>
My question is : how can I add style to props.icon in basewidget ?
Can I create a styled component based on props.icon and add a common css property ?
If it's not possible what is the best solution ?
Thanks,
Jef
When you are passing in icon={<Icon2 />} you are actually passing a JSX element, which cannot be styled from the other side with styled-components because it is not a component. To style a component, it needs to take the className as a prop. In this case, your best bet is to write a styled-component wrapper dev and let the styles cascade to your {props.icon}
But because you are not passing in any props to Icon you could easily pass in the component as a prop making it stylable.
<BaseWidget icon={Icon1} />
Where you are receiving it:
import styled from "styled-components";
const StyledIcon = styled.i``;
const BaseWidget = (props) => {
const { Icon } = props.icon;
return (
<StyledIcon as={Icon} />
);
}
as Docs