react styled-component nested item's props - css

My problem is about props. I want to use nested components with props in styled-components. For example:
const MenuItem = ({ item }) => {
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);
const isActive = router?.asPath === item?.path;
return (
<MenuItemWrapper key={item?.slug} onClick={() => setIsOpen(!isOpen)}>
<Link href={item?.path}>
<InnerMenuItem isActive={isActive}>
{item?.prefix && <Prefix>{item?.prefix}</Prefix>}
{item?.label}
{item?.children && <RightArrow isOpen={isOpen} />}
</InnerMenuItem>
</Link>
<Children>
{
item?.children?.map((child) => <MenuItem item={child} />)
}
</Children>
</MenuItemWrapper>
);
};
export default MenuItem;
this is MenuItem component. I use MenuItem component as a recursion component.
in styled-component i tried this but it doesnt work. I want to apply different style in Children > InnerMenuItem but it not working
export const Children = styled.div`
display: flex;
flex-direction: column;
margin-left: 65px;
${MenuItemWrapper} {
font-size: 16px;
padding: 9px 0;
&:not(:first-child) {
border-top:none;
}
}
${InnerMenuItem} {
${({ isActive }) => // HOW CAN I USE THIS PROPS HERE
isActive &&
css`
color: orange
`};
}
`;

from styled components official documentations:
"If the styled target is a simple element (e.g. styled.div), styled-components passes through any known HTML attribute to the DOM. If it is a custom React component (e.g. styled(MyComponent)), styled-components passes through all props."
example :
const Input = styled.input`
color: ${props => props.inputColor || "palevioletred"};
`;
return(
<Input inputColor="rebeccapurple" />
)
another way is by Extending Styles, example :
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
return(
<div>
<Button>Normal Button</Button>
<TomatoButton>Tomato Button</TomatoButton>
</div>
);
more about styled-components read here

have you tried
${InnerMenuItem} {
color: ${({isActive})=> isActive ? 'orange' : undefined}
};

Related

props is not applied although passed it to styled component

What I want to do
Changing colors depending on props passed from a component using styled component.
Problem
SmallButton component definitely gets props but it doesn't change like props indicates.
I would like to change styles of SmallButton depending on props that a component gives to the button component.
SmallButton has these props but it doesn't change at all.
I would like you to teach me how to solve it.
Thank you very much.
=== ==== === ===
My code is like this.
Header.jsx
render() {
return (
<>
<Wrapper>
{/* CSS Grid( 1 : 1 : 1) 左 */}
<Image src={Logo} alt="" />
{/* CSS Grid( 1 : 1 : 1) 中央 */}
<SearchBox />
{/* CSS Grid( 1 : 1 : 1) 右 */}
{this.props.isAuthenticated ? (
<>
<div>
<MessageToUserDiv>
<span>Hello {this.state.loginUser.username}</span> 
<LogoutButton onClick={this.handleLogout}>Logout</LogoutButton>
</MessageToUserDiv>
<AuthButtonDiv>
<SmallButton
btn_border="#466A80"
btn_back="#466A80"
btn_text_color="#D9F1FF"
btn_name="Post"
btn_click={this.jumpToPostGive}
/>
<SmallButton
btn_border="#466A80"
btn_back="#8DD6FF"
btn_text_color="#466A80"
btn_name="Info"
btn_click=""
/>
</AuthButtonDiv>
</div>
</>
) : (
<>
<div>
<p>Hello Guest</p>
<AuthButtonDiv>
<SmallButton btn_name="Register" btn_click={this.jumpToRegister} />
<SmallButton btn_name="Login" btn_click={this.jumpToLogin} />
</AuthButtonDiv>
</div>
</>
)}
</Wrapper>
</>
);
}
}
const Wrapper = styled.div`
background-color: #8dd6ff;
width: 100%;
display: grid;
grid-template-columns: 1fr 2.3fr 1fr;
padding: 10px 5px 5px 5px;
`;
const Image = styled.img`
width: 230px;
margin-top: 5px;
`;
const MessageToUserDiv = styled.div`
font-size: 13px;
text-align: right;
height: 20%;
`;
const LogoutButton = styled.button`
color: #6e787f;
width: 30%;
`;
const AuthButtonDiv = styled.div`
display: flex;
justify-content: space-around;
align-items: flex-end;
height: 80%;
`;
SmallButton.jsx
class SmallButton extends Component {
constructor(props) {
super(props);
}
render() {
return (
<StyledButton
type={this.props.btn_type}
onClick={this.props.btn_click}
onSubmit={this.props.btn_submit}
disabled={this.props.btn_disable}
>
{this.props.btn_name}
</StyledButton>
);
}
}
const Colors = {
main: '#8DD6FF',
characters: '#6C7880',
subcolor1: '#D9F1FF',
accent1: '#70AACC',
accent2: '#466A80',
};
const StyledButton = styled.button`
font-size: 1.18em;
border-radius: 7px;
height: 45px;
width: 100px;
padding: 2px 3.5px;
border: solid 2.5px;
border-color: ${(props) => props.btn_border};
background: ${(props) => props.btn_back};
color: ${(props) => props.btn_text_color};
`;
spread the rest for your props to StyledButton like this:
<StyledButton
type={this.props.btn_type}
onClick={this.props.btn_click}
onSubmit={this.props.btn_submit}
disabled={this.props.btn_disable}
{...this.props}
>
{this.props.btn_name}
</StyledButton>

Approach to creating variants with styled components

What is the best way to create variants using styled components? Heres what i am currently doing.
const ButtonStyle = styled.button`
padding:8px 20px;
border:none;
outline:none;
font-weight:${props => props.theme.font.headerFontWeight};
font-size:${props => props.theme.font.headerFontSize};
display:block;
&:hover{
cursor:pointer;
}
${({ variant }) =>
variant == 'header' && css`
background-color:${props => props.theme.colors.lightblue};
color:${({ theme }) => theme.colors.white};
&:active{
background-color:${props => props.theme.colors.blue}
}
`
}
${({ variant }) =>
variant == 'white' && css`
background-color:white;
color:${({ theme }) => theme.colors.lightblue};
&:active{
color:${props => props.theme.colors.blue}
}
`
}
`;
I cannot tell if this is the standard way of doing things.
I have also been using other components as bases to create other components from while changing a few things
eg
const InnerDiv = styled(otherComponent)`
position: unset;
background-color: red;
overflow-x: hidden;
display: flex;
`;
Which is the better approach? Are there any better alternatives?
Inspired by previous solutions, I want to share what I came up with:
import styled, { css, DefaultTheme } from 'styled-components';
const variantStyles = (theme: DefaultTheme, variant = 'primary') =>
({
primary: css`
color: ${theme.colors.light};
background: ${theme.colors.primary};
border: 1px solid ${theme.colors.primary};
`,
}[variant]);
const Button = styled.button<{ variant: string }>`
padding: 1rem;
font-size: 0.875rem;
transition: all 0.3s;
cursor: pointer;
${({ theme, variant }) => variantStyles(theme, variant)}
&:active {
transform: translateY(1.5px);
}
`;
export default Button;
For now it contains only primary and its the default one, by you can add more variants by adding new object to variantStyles object
Then you can use it by passing the variant as a prop or keep the default by not passing any variant.
import { Button } from './HeroSection.styles';
<Button variant="primary">Start Learning</Button>
This is just my opinion:
I don't think we can do anything very different from what you did.
A different way that I thought, would be to create an options object to map the possibilities of the variant, like this:
const variantOptions = {
header: {
backgroundColor: theme.colors.lightblue,
color: theme.colors.white,
active: theme.colors.blue,
},
white: {
backgroundColor: "white",
color: theme.colors.lightblue,
active: theme.colors.blue,
},
};
And use it in your style component like this:
const ButtonStyle = styled.button`
padding: 8px 20px;
border: none;
outline: none;
font-weight: ${(props) => props.theme.font.headerFontWeight};
font-size: ${(props) => props.theme.font.headerFontSize};
display: block;
&:hover {
cursor: pointer;
}
${({ variant }) =>
variant &&
variantOptions[variant] &&
css`
background-color: ${variantOptions[variant].backgroundColor};
color: ${variantOptions[variant].color};
&:active {
color: ${variantOptions[variant].active};
}
`}
`;
And all of this buttons will work:
<ButtonStyle variant="*wrong*">Button</ButtonStyle>
<ButtonStyle variant="header">Button</ButtonStyle>
<ButtonStyle variant="white">Button</ButtonStyle>
<ButtonStyle>Button</ButtonStyle>
When dealing with Styled Component variants here is what I like to do to keep things organised and scalable.
If the variants are stored within the same file I am using the inheritance properties:
const DefaultButton = styled.button`
color: ${(props) => props.theme.primary};
`;
const ButtonFlashy = styled(DefaultButton)`
color: fuchsia;
`;
const ButtonDisabled = styled(DefaultButton)`
color: ${(props) => props.theme.grey};
`;
If if we are talking about a reusable components I would use this technique:
import styled from 'styled-components';
// Note that having a default class is important
const StyledCTA = ({ className = 'default', children }) => {
return <Wrapper className={className}>{children}</Wrapper>;
};
/*
* Default Button styles
*/
const Wrapper = styled.button`
color: #000;
`;
/*
* Custom Button Variant 1
*/
export const StyledCTAFushia = styled(StyledCTA)`
&& {
color: fuchsia;
}
`;
/*
* Custom Button Variant 2
*/
export const StyledCTADisabled = styled(StyledCTA)`
&& {
color: ${(props) => props.theme.colors.grey.light};
}
`;
export default StyledCTA;
Usage:
import StyledCTA, { StyledCTADisabled, StyledCTAFushia } from 'components/StyledCTA';
const Page = () => {
return (
<>
<StyledCTA>Default CTA</StyledCTA>
<StyledCTADisabled>Disable CTA</StyledCTADisabled>
<StyledCTAFushia>Fuchsia CTA</StyledCTAFushia>
</>
)
};
Read more about this in the blog posts I created on the subject here and there.
There are many ways to do this. one simple way is to use the package called Styled-components-modifiers. documentation is simple and straightforward.
https://www.npmjs.com/package/styled-components-modifiers
Simple usage example:
import { applyStyleModifiers } from 'styled-components-modifiers';
export const TEXT_MODIFIERS = {
success: () => `
color: #118D4E;
`,
warning: () => `
color: #DBC72A;
`,
error: () => `
color: #DB2A30;
`,
};
export const Heading = styled.h2`
color: #28293d;
font-weight: 600;
${applyStyleModifiers(TEXT_MODIFIERS)};
`;
In the Component - import Heading and use modifier prop to select the variants.
<Heading modifiers='success'>
Hello Buddy!!
</Heading>
Styled components are usually used with Styled system that supports variants and other nice features that enhance Styled components. In the example below Button prop variant automatically is mapped to keys of variants object:
const buttonVariant = ({ theme }) =>
variant({
variants: {
header: {
backgroundColor: theme.colors.lightblue,
color: theme.colors.white,
active: theme.colors.blue,
},
white: {
backgroundColor: 'white',
color: theme.colors.lightblue,
active: theme.colors.blue,
},
},
})
const Button = styled.button`
${(props) => buttonVariant(props)}
`
Styled System Variants: https://styled-system.com/variants
Use the variant API to apply styles to a component based on a single prop. This can be a handy way to support slight stylistic variations in button or typography components.
Import the variant function and pass variant style objects in your component definition. When defining variants inline, you can use Styled System like syntax to pick up values from your theme.
// example Button with variants
import styled from 'styled-components'
import { variant } from 'styled-system'
const Button = styled('button')(
{
appearance: 'none',
fontFamily: 'inherit',
},
variant({
variants: {
primary: {
color: 'white',
bg: 'primary',
},
secondary: {
color: 'white',
bg: 'secondary',
},
}
})
)

React: Styled-components loading button

I am using React- typescript for my app. For Styling I am using Styled-components. I have created one global button component. Inside the button component i used loader. My target is when user will click the button it will display loading. From the parent component I have created one fake api call and in there I added settime-out 5s. But when I click the button it does not display the loader which was in Button component. From the parent component set-time out works and it display in my console. I don't know why it does not the display the loading ..... Also i added the disabled option when fake api will call button should be disabled. That logic also does not work.
Here is the Button component
import * as React from "react";
import styled, { css } from "styled-components";
interface IButtonProps {
children?: React.ReactChild;
className?: string;
size?: "small" | "medium" | "big";
themes?: "primary" | "secondary" | "dark" | "light";
disabled?: boolean;
loading?: boolean;
style?: React.CSSProperties;
onClick?: () => void;
onSubmit?: () => void;
}
const Button = ({
children,
className,
size,
themes,
disabled,
loading,
style,
onClick,
onSubmit
}: IButtonProps) => (
<button
className={className}
onClick={onClick}
onSubmit={onSubmit}
style={
disabled && disabled
? { opacity: 0.5, pointerEvents: `none` }
: loading ? { ...style, pointerEvents: `none` } : //This is my disabled condition.
style
}
>
{loading ? <p>loading...</p> : children} //This is my loading condition.
</button>
);
const sizes = {
small: css`
padding: 5px 20px;
font-size: 12px;
`,
medium: css`
padding: 10px 30px;
font-size: 14px;
`,
big: css`
padding: 15px 40px;
font-size: 18px;
`
};
const ButtonThemes = {
primary: css`
border: 1px solid tomato;
background: tomato;
color: white;
`,
secondary: css`
border: 1px solid palevioletred;
background: palevioletred;
color: white;
`,
dark: css`
border: 1px solid #273444;
background: #273444;
color: white;
`,
light: css`
border: 1px solid #eff2f7;
background: #f9fafc;
color: #273444;
`
};
const StyledButton = styled(Button)`
${({ size = "small" }) => sizes[size]};
${({ themes = "primary" }) => ButtonThemes[themes]};
outline: none;
border-radius: 5px;
cursor: pointer;
`;
export default StyledButton;
This is my parent component. Where I used settimeout and import Button component.
const handleClick = () => {
setTimeout(() => {
console.log("check it out") //It does display when the button click
}, 5000);
}
//This is the Button
<Button size="medium" themes="primary" onClick={handleClick}>click</Button>
You should create a loading state by using useState hook if your parent component is function component, if using class component you can define a state in constructor like this this.state = { loading: false } in your parent component, and set it to true in handleClick and pass the state as prop to your Button component:
// In case function component
const [loading, setLoading] = useState(false);
const handleClick = () => {
setLoading(true);
setTimeout(() => {
setLoading(false); // When finish, set it to false
console.log("check it out") //It does display when the button click
}, 5000);
}
//This is the Button
<Button
size="medium"
themes="primary"
onClick={handleClick}
loading={loading}
>
click
</Button>

CSS to React components

I started rewriting CSS into React Components but i've encountered a problem, I do know how to do this (using styled-components btw ) :
You have 5 ways to styling component in React.
Every approach have pros & cons(personally I use 4-th ways.)
1.Inline Css
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
2. CSS in JS
const Button = (props) => (
<button className={ 'large' in props && 'large' }>
{ props.children }
<style jsx>{`
button {
padding: 20px;
background: #eee;
color: #999
}
.large {
padding: 50px
}
`}</style>
</button>
)
/* Creates a regular button */
<Button>Hi</Button>
/* Creates a large button */
<Button large>Big</Button>
3. Styled Components
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
render(
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
4. Css Modules (scss || sass)
#width: 10px;
#height: #width + 10px;
#header {
width: #width;
height: #height;
}
5. Stylable - https://github.com/wix/stylable
import React from 'react';
import styled, {css} from 'styled-components';
const Info = styled.div`
display: flex;
// etc.
${props => props.title && css`font-size: 15px`}
`
const Box = styled.div`
&:first-of-type {
// some more css
}
`
// usage
<Info title>some info</Info>
I recommend you follow the official docs as stated in comments by #Mikkel

Styled component does not inherit styles when using "as" attribute

I'm using styled-system with styled components and have a basic case like this:
const buttonFont = {
fontFamily: "Chilanka"
};
// style a boilerplate for text
const Text = styled.div`
${typography}
${color}
`;
// button blueprint
const Button = ({ children, ...rest }) => {
return (
<Text as="button" {...buttonFont } {...rest}>
{children}
</Text>
);
};
// styled button using button
const StyledButton = styled(Button)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
// When using "as" this component does not includes buttonFont styles
const StyledLabel = styled(StyledButton).attrs({
as: "label"
})``;
I want to create a StyledLabel which will inherit all styles from StyledButton, but change tag to label. But StyledLabel does not get the buttonFont styles when using "as" attribute.
Please see live example here: demo
I'm not sure what your end goal is, but these 2 examples worked in terms of inheritance. However, they might not help with your plan for composition:
import React from "react";
import styled, {css} from "styled-components";
import { typography, color } from "styled-system";
import ReactDOM from "react-dom";
import "./styles.css";
const buttonFont = {
fontFamily: "Chilanka"
};
const Text = styled.div`
${typography}
${color}
margin: 24px;
`;
const StyledButton = styled(Text)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
const StyledLabel = styled(StyledButton)`
color: yellow;
`;
const __Text = styled.div`
${typography(buttonFont)}
${color}
margin: 24px;
`;
const __StyledButton = styled(__Text)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
const __StyledLabel = styled(__StyledButton)`
color: yellow;
`;
function App() {
return (
<div className="App">
<StyledButton as="button" {...buttonFont}>styled button</StyledButton>
<StyledLabel as="label" {...buttonFont}>Does inherit styled font with "as"</StyledLabel>
<br />
<br />
<br />
<__StyledButton as="button">styled button</__StyledButton>
<__StyledLabel as="label">Does inherit styled font with "as"</__StyledLabel>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Resources