How to use Styled Components for a component passed as props - javascript

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

Related

How to shorten passed items in components

I use styled-components each component from styled-components I pass in other components, in order to apply them, the problem is that my code looks ugly because every component style I pass in other components it looks like this
SideBarStyledComponents.js
export default function SideBarStyledComponents(props) {
const {SideBarValue} = React.useContext(CounterContext);
const [SideBarThemeValue] = SideBarValue;
const PageColor = SideBarThemeValue && SideBarThemeValue.PageContentColor;
const AlertBg = SideBarThemeValue && SideBarThemeValue.AlertBackground;
const LessonContainers = styled.div`
margin: 2rem 0 2rem 0;
`;
const LessonSideBarTitle = styled.h1`
font-size: 1.8rem;
font-weight: 500;
color: ${(PageColor ? PageColor : "#2c3e50")};
font-family: 'Roboto';
margin-top: 1rem;
`;
return(
<RoutesPage {...props} LessonContainers={LessonContainers} SideBarThemeValue={SideBarThemeValue}
LessonSideBarTitle={LessonSideBarTitle}/>
);
}
RoutesPage.js
function RoutesPage(props) {
const {path} = props.path;
const routes = [
{
path: `${path}/Introduction`,
component: () => <Introduction {...props} />
},
{
path: `${path}/Creating Your First Javascript`,
exact: true,
component: () => <CreatingFirstJS {...props} />
},
{
path: `${path}/Guardian`,
component: () => <h2>Shoelaces</h2>
}
];
return (
<>
<Switch>
{routes.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
))}
</Switch>
</>
);
}
Please pay attention, you have noticed every style I pass to the components and so every time I create a new component every style I have to pass this way I will have many components since I am creating a sidebar I want to know if there is a way to get rid of this
You should define all the styled components outside in a separate file (or multiple files). And then you should import those styled components directly within your component where you are going to use it.
Passing them as props is a bad practice.
For example you can create a file called 'StyledComponents.js' and export all your styled components.
...
export const LessonContainers = styled.div`
margin: 2rem 0 2rem 0;
`;
export const LessonSideBarTitle = ({children}) => {
const {SideBarValue} = React.useContext(CounterContext);
const [SideBarThemeValue] = SideBarValue;
const PageColor = SideBarThemeValue && SideBarThemeValue.PageContentColor;
const H1 = styled.h1`
font-size: 1.8rem;
font-weight: 500;
color: ${(PageColor ? PageColor : "#2c3e50")};
font-family: 'Roboto';
margin-top: 1rem;
`;
return <H1>{children}</H1>
}
...
And now in the Introduction or CreatingFirstJS component, you can just import the necessary styled components like so:
import {LessonSideBarTitle} from 'path/to/StyledComponents';
Another way is to take advantage of the styled object properties, to remove the ugly long interpolation of props by destructuring the props
import styled from "styled-components"
const Button = styled.button(
({ bgColor, fontColor }) => `
background: ${bgColor};
color: ${fontColor};
`
);
function App() {
return (
<Button bgColor="#000" fontColor="#fff"> Hello World </Button>
)
}
Example in codesandbox

How to use forwardedAs prop with styled-components? Using forwardedAs prop with typescript

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>
);

Passing React State Between Imported Components

I am trying to pass state from parent to child using React, however both components are imported and therefor the state variables of the parent component are not declared.
I have two components both exported from the same file. The first component is a wrapper for the second. This component has a useEffect function which find its height and width and set these values to hook state.
export const TooltipWrapper = ({ children, ariaLabel, ...props }) => {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current && ref.current.getBoundingClientRect().width) {
setWidth(ref.current.getBoundingClientRect().width);
}
if (ref.current && ref.current.getBoundingClientRect().height) {
setHeight(ref.current.getBoundingClientRect().height);
}
});
return <TooltipDiv>{children}</TooltipDiv>;
The next component which is exported from the same file looks like this
export const Tooltip = ({
ariaLabel,
icon,
iconDescription,
text,
modifiers,
wrapperWidth,
}) => {
return (
<TooltipContainer
aria-label={ariaLabel}
width={wrapperWidth}
>
<TooltipArrow data-testid="tooltip-arrow" modifiers={modifiers} />
<TooltipLabel
aria-label={ariaLabel}
>
{text}
</TooltipLabel>
</TooltipContainer>
);
};
The component Tooltip is expecting a prop wrapperWidth. This is where I want to pass in the width hook value from the TooltipWrapper component.
Both components are imported into my App component
import React from "react";
import { GlobalStyle } from "./pattern-library/utils";
import { Tooltip, TooltipWrapper } from "./pattern-library/components/";
function App() {
return (
<div className="App">
<div style={{ padding: "2rem", position: "relative" }}>
<TooltipWrapper>
<button style={{ position: "relative" }}>click </button>
<Tooltip
modifiers={["right"]}
text="changing width"
wrapperWidth={width}
/>
</TooltipWrapper>
</div>
</div>
);
}
Here I am told that width is not defined, which I expect since I'm not declaring width in this file.
Does anyone have an idea of how I can access the width and height state value for the parent component within the App file?
Render Props could work:
Add a renderTooltip prop to <TooltipWrapper>:
<TooltipWrapper renderTooltip={({ width }) => <Tooltip ...existing wrapperWidth={width} />}>
<button style={{ position: 'relative' }}>click</button>
</TooltipWrapper>
NB. ...existing is just the other props you are using with Tooltip
And then update the return of <TooltipWrapper>:
return (
<TooltipDiv>
{children}
props.renderTooltip({ width });
</TooltipDiv>
);

Styled components nested rules don't work

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{}
`

How to use styled-components in react component

Total newbie on using styled-components. I'm wondering what's the usage of it? How should I implement component life cycle methods after styling it? For simplicity sake I've removed all the other style.
import styled from 'styled-components';
const Button = styled.button`
background-color: 'green'
`
export default Button;
I'm wondering how do I further working on this Button component?
Traditionally we can declare a class-based component and implement some lifecycle methods, but now with this styled-components, I'm not really sure how to combine them together as they are really the single Button Component?
UPDATES:
Full sourcecode for Button.js. By having the below code, all styles will be gone and I can't understand the problem
import React from 'react';
import styled from 'styled-components';
// import Button from 'react-bootstrap/Button';
import color from '../config/color';
const Button = ({ children, onPress }) => (
<button type="button" onPress={onPress}>{children}</button>
);
const StyledButton = styled(Button)`
width: 12rem;
height: 54px;
font-size: 1rem;
background-color: ${(props) => {
if (props.inverted) return 'white';
if (props.disabled) return color.disabled;
return (props.color || color.primary);
}};
color: ${(props) => {
if (props.disabled) return color.disabledText;
if (props.inverted) return (props.color || color.primary);
return 'white';
}};
border:${(props) => (props.inverted ? `2px solid ${props.color || color.primary}` : 'none')};
border-radius: 60px;
&:hover {
filter: ${(props) => (props.inverted || props.disabled ? 'none' : 'brightness(95%)')}
}
`;
export default StyledButton;
In order to style a custom react component you can pass on the custom component name as argument to styled. According to the doc:
The styled method works perfectly on all of your own or any
third-party component, as long as they attach the passed className
prop to a DOM element.
import React from 'react';
import styled from 'styled-components';
// import Button from 'react-bootstrap/Button';
import color from '../config/color';
const Button = ({ children, className onPress }) => (
<button type="button" className={className} onPress={onPress}>{children}</button>
);
const StyledButton = styled(Button)`
width: 12rem;
height: 54px;
font-size: 1rem;
background-color: ${(props) => {
if (props.inverted) return 'white';
if (props.disabled) return color.disabled;
return (props.color || color.primary);
}};
color: ${(props) => {
if (props.disabled) return color.disabledText;
if (props.inverted) return (props.color || color.primary);
return 'white';
}};
border:${(props) => (props.inverted ? `2px solid ${props.color || color.primary}` : 'none')};
border-radius: 60px;
&:hover {
filter: ${(props) => (props.inverted || props.disabled ? 'none' : 'brightness(95%)')}
}
`;
export default StyledButton;
Read the styled-component documentation for more details on styling any component
Let's rename the styled button component to reduce confusion between the 2 similarly named components.
styled-button.tsx:
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: 'green'
`
export default StyledButton;
When you import the styled button component into your Button component, you can actually use make use of it the way you usually do when you are working with traditional HTML <button> elements, as its props are exposed and available on the styled component as well.
button.tsx:
import StyledButton from './StyledButton'
class Button extends React.Component {
componentDidMount() {
const { someProps, otherProps } = this.props;
// some lifecycle logic
}
handleClick() {
// do the rest
}
render() {
return <StyledButton onClick={() = this.handleClick()} />;
}
}
If you want, you can even pass in props from the parent Button component, to the child StyledButton component. This will allow you to customise it.
render() {
const { color } = this.props;
return <StyledButton background={color} onClick={() = this.handleClick()} />;
}
And on your StyledButton component, you just need to make the following changes:
const StyledButton = styled.button`
background-color: ${({ color }) => color || 'green'}
`
What other answers lack is for styling custom components like Button you have to pass a className prop thought it.
The styling is injected through className property.
const ButtonDefaultStyle = styled.button`
width: 5rem;
`;
const Button = ({ className, children, onPress }) => (
<ButtonDefaultStyle className={className} type="button" onPress={onPress}>
{children}
</ButtonDefaultStyle>
);
export default Button;
Then the styles can be applied:
import Button from './Button.js'
// Will override width: 5rem;
const StyledButton = styled(Button)`
width: 12rem;
`;

Categories

Resources