How to access child's props from parent's styled-component? - javascript

I have kind of this construction:
<Wrapper activeTextColor="red">
<Text active={true}>Text 1</Text>
<Text active={false}>Text 2</Text>
</Wrapper>
Styled-components should look like this:
const Text = styled.p``;
const Wrapper = styled.div`
${Text} {
${props =>
props.activeTextColor &&
css`
/* How to make color red based on "active" attribute of Text element? */
`}
}
`;
How to access child's props from parent's styled-component here?
Here is a live example

You can't (as far as I can tell). But you can access the parent's props from the child component (the other way around). That seems to accomplish what you are trying to do.
Short answer:
You have to pass the parent prop to the child component.
In a parent component <Wrapper /> you would have to clone your children and pass the activeTextColor to the children:
const StyledWrapper = styled.div``;
class Wrapper extends React.Component {
render() {
const { children, activeTextColor } = this.props;
return (
<StyledWrapper activeTextColor={activeTextColor}>
{React.Children.map(children, child =>
React.cloneElement(child, {
activeTextColor: activeTextColor
})
)}
</StyledWrapper>
);
}
}
Both activeTextColor and active are now accessible from the Text component.
const Text = styled.p`
${props => css`
color: ${props.active ? activeTextColor : "#000"};
`}
`;
Another Option:
In the case above, it might make more sense to go with a ThemeProvider/ThemeConsumer. If you know an activeTextColor is going to be red (maybe you are dealing with design tokens), then access the active colors with:
${props => css`
background: ${props.active ? props.theme.activeTextColor : '#000'};
`}
Detailed Answer (and why someone would want to do this):
This extends the Short Answer above. At some point you might need to access the parent props in the parent component, and both the child and parent props in the child component.
A real world example would be something like Tabs. I have two different styles / variants of Tabs, with both the Tabs container component and Tab needing its own styles depending its own props. It is one component styled two different ways.
Nesting the styled-components won't work. So you end up with something like this.
const StyledTabs = styled.div`
display: flex;
justify-content: flex-start;
${props =>
props.variant === "wizard" &&
css`
justify-content: space-between;
`}
`;
const StyledTab = styled.p`
font-size: 14px;
white-space: nowrap;
font-family: sans-serif;
border: 1px solid #ddd;
padding: 15px;
${props => css`
background: ${props.active ? "#fff" : "#f6f6f6"};
`}
${props =>
props.variant === "box" &&
css`
& {
border-right: 0 none;
&:last-child {
border-right: 1px solid #ddd;
}
border-top: ${props.active
? "2px solid lightseagreen"
: "1px solid #ddd"};
border-bottom: ${props.active ? "none" : "1px solid #ddd"};
}
`}
${props =>
props.variant === "wizard" &&
css`
& {
margin-right: 20px;
text-align: center;
line-height: 40px;
height: 40px;
width: 40px;
border-radius: 50%;
color: ${props.active ? "#fff" : "#000"};
${props.active && "background: lightseagreen"};
}
`}
`;
class Tabs extends React.Component {
render() {
const { children, variant } = this.props;
return (
<StyledTabs variant={variant}>
{React.Children.map(children, child =>
React.cloneElement(child, {
variant: variant
})
)}
</StyledTabs>
);
}
}
class Tab extends React.Component {
render() {
const { children, variant, active } = this.props;
return (
<StyledTab variant={variant} active={active}>
{children}
</StyledTab>
);
}
}
const App = () => (
<div>
<Tabs variant="box">
<Tab active={true}>Tab 1</Tab>
<Tab>Tab 2</Tab>
<Tab>Tab 3</Tab>
</Tabs>
<Tabs variant="wizard">
<Tab active={true}>Step 1</Tab>
<Tab>Step 2</Tab>
<Tab>Step 3</Tab>
</Tabs>
</div>
);
render(<App />, document.getElementById("root"));
Full example:
https://codesandbox.io/s/upbeat-thunder-wfo2m
Related issue on styled-component's GitHub:
https://github.com/styled-components/styled-components/issues/1193
There are quite a few related questions on StackOverflow, but I don't think there are many clear answers:
react-native with styled-components parent prop
How to style a child component from a parent styled component, considering passed props

const Text = styled.p`color: ${props => props.active ? "red" : "palevioletred"};`;

Related

How do I rotate the icon based on the isActive state?

I do have the icon component like that:
<Styled.Icon
color={isActive ? activeColor : color}
activeColor={activeColor}
>
<Icon name="arrowDown" />
</Styled.Icon>
I need to rotate this icon based on the isActive state. How can I do this in styled components? I tried something like this:
export const Icon = styled.div`
${(isActive) =>
isActive &&
`
transform: rotate(180deg);
`}
padding: 20px 20px 0px 20px;
`
But it is not working. Any help would be appreciated.
you can pass a props to your component called isActive as an example and then based on its value you set the css transform value.
<Styled.Icon
isActive={isActive}
>
<Icon name="arrowDown" />
</Styled.Icon>
export const Icon = styled.div`
transform: ${props => props.isActive ? "rotate(180deg)" : "rotate(0)"};
padding: 20px 20px 0px 20px;
`
here is how to adapt your style based on the props from the official documentation.

How to test the state of a functional component in React with Jest (No Enzyme)

Hi everybody how can I test the state of a functional component from the useState hook?
This my component:
const Container = styled.div
display: flex;
flex: 1;
flex-direction: row;
align-items: ${props => props.hasSubtitle ? 'flex-start' : 'center'};
opacity: ${props => props.disabled ? 0.5 : 1.0};
${props => props.style}
const Button = styled.button
padding: 0;
border: none;
background: none;
cursor: pointer;
outline: none;
const TextContainer = styled.div
display: flex;
flex: 1;
flex-direction: column;
margin-left: 12px;
const CheckBox = props => {
const [selected, setSelected] = useState(false);
useEffect(() => {
setSelected(props.selected);
}, [props.selected]);
return (
<Container data-testid={'checkbox'} style={props.style} hasSubtitle={props.subtitle}>
<Button
data-testid={'checkBtn'}
onClick={() => {
if(!props.disabled){
setSelected(!selected);
if(props.onChange) props.onChange(!selected);
}
}}
>
{selected ? <CheckBoxOn/> : <CheckBoxOff/>}
</Button>
<TextContainer>
<Text style={{fontSize: 16}}>
{props.title}
</Text>
{props.subtitle && <Text style={{fontSize: 12}}>{props.subtitle}</Text>}
</TextContainer>
</Container>
);
};
export default CheckBox;
How can I check the value of the state when I render the component with the selected props value to false and when I render it with selected prop to true?
And how I can test if the click event on the button trigger setSelected()? By the way we are not able to use Enzyme
Checking state in your tests lately has been considered a bad practice. What people generally recommend is to check the consequences of a state change: in your case, you could check the presence of CheckBoxOn.

Trying to pass 'active' class styles to a nested SVG that's passed as a prop to button element

I have a Menu/Tabs React component I'm building that I've set up to receive a combination of both an SVG and a string of text as the title prop in order to display as the tab name.
The issue I'm having is that I have an active class that colors the active tab text blue, but not the SVG which remains black.
Desired Behavior: Have active class CSS stylings applied to both text and SVG elements so that they are both blue when active and both black when they are inactive
Current Behavior: Active class stylings are applied to the text but not the SVG. When tab is active text turns blue, while the SVG remains black
Here's a CodeSandbox demonstrating the problem:
My Current Tabs Component:
// #ts-nocheck
import React, { useState } from 'react';
import styled from '../../styles/styled';
type Props = {
children: React.ReactNode;
};
export const Tabs = (props: Props) => {
const { children } = props;
const [tab, setTab] = useState(0);
const childrenList = React.Children.toArray(children);
const tabs = childrenList.map((child, idx) => {
const title = (child as any).props.title ?? idx;
return (
<StyledTabs key={title} className={tab === idx ? 'activeTab' : ''} onClick={() => setTab(idx)}>
{title}
</StyledTabs>
);
});
const current = childrenList[tab];
return (
<div>
<div>{tabs}</div>
<div>{current}</div>
</div>
);
};
const StyledTabs = styled.button`
margin: 0 10px;
padding: 0 10px;
padding-bottom: 5px;
border: none;
background: transparent;
display: inline-block;
font-weight: 700;
text-transform: uppercase;
&.activeTab {
color: #1471da;
border-bottom: 1px solid #1471da;
outline: none;
padding-bottom: 5px;
}
`;
Page where Tabs Component is used :
const OverviewIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="35" height="35">
<path d="M0 0h16v16H0zM19 0h16v16H19zM0 19h16v16H0zM19 19h16v16H19z" />
</svg>
);
const OverviewTab = () => (
<>
<OverviewIcon />
<span>OVERVIEW</span>
</>
);
...
<Tabs>
<div title={<OverviewTab />}>
<ContentSection></ContentSection>
</div>
<div title={'ADDITIONAL CONTACTS'}>
<h1>CONTACTS</h1>
</div>
</Tabs>
To change an svg color, use fill instead within your scss :
&.activeTab {
color: #1471da;
fill: #1471da;
border-bottom: 1px solid #1471da;
outline: none;
padding-bottom: 5px;
}

Create new component and inherit styles from styled-component

const Button = styled.button`
display: inline-block;
width: 300px;
background-color: black;
`
const ButtonHref = styled.a`
${Button}
`
So I have two styled-components. I want to inherit 'Button' styles but create another tag. I use react-emotion. How can I do this?
There are a few options here, using composition, styled components, or with props. The second option is probably what you want, but I've provided two other options as well.
1. Using composition
const baseButton = css`
color: white;
background-color: black;
`
const fancyButton = css`
background-color: red;
`
render() {
return (
<div>
<button css={baseButton}></button>
<button css={[baseButton, fancyButton]}></button>
</div>
)
}
The second button will have the baseButton and specialButton styles.
Or...
const baseButton = css`
color: white;
background-color: black;
`
const fancyButton = css`
${baseButton};
background-color: red;
`
render() {
return (
<div>
<button css={baseButton}></button>
<button css={fancyButton}></button>
</div>
)
}
2. Using styled components
const Button = styled.button`
color: white;
background-color: black;
`
const Fancy = styled(Button)`
background-color: red;
`
render() {
return (
<div>
<Button>Button</Button>
<Fancy>Fancy</Fancy>
</div>
)
}
This works for any component that accepts the className prop, which button does.
3. Using props
const Button = styled.button`
color: white;
background-color: ${props => props.fancy ? 'red' : 'black'};
`
render() {
return (
<div>
<Button>Button</Button>
<Button fancy>Fancy</Button>
</div>
)
)
If you just want an a that has the exact styles as your Button then you can do <Button as=“a” />
You could also add some custom css like this:
const ButtonBase = styled.button`
// some css here
`
const SmallButtonCustom = `
// some css here
`
const SmallButton = styled(ButtonBase)`
${SmallButtonCustom};
`

React.js child component not updating styling derived from props, why? (styled-components)

I am new to react.js and I am trying to get my head around conditional styling depending on props passed down from parent components.
I'm trying to make a button that has styling differences depending on whether the 'disabled' prop is true or false. If the button is disabled (true) it should appear grey, otherwise, it's blue.
This is the code I have so far, although it is not working and I'm not sure why.
Parent component
import React from 'react';
import { storiesOf } from '#storybook/react';
import { action } from '#storybook/addon-actions';
import { linkTo } from '#storybook/addon-links';
import { Welcome } from '#storybook/react/demo';
// Buttons
import Primary from '../components/ButtonPrimary'
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
storiesOf('Buttons')
.add('Primary', () => <Primary label="Default" disabled="false"></Primary>)
Child component
import React from "react";
import styled from 'styled-components';
const Button = styled.button`
background-color: ${props => props.disabled ? '#EDEDED' : '#0076C0'};
border: ${props => props.disabled ? '1px solid #DADADA' : 'none'};
color: ${props => props.disabled ? '#818181' : '#FFFFFF'};
cursor: ${props => props.disabled ? 'unset' : 'pointer'};
border-radius: 2px;
font-family: Roboto-Regular;
font-size: 16px;
padding: 6px 32px;
font-family: roboto, helvetica, sans-serif;
font-size: 18px;
&:focus {
outline: none;
}
&:hover {
box-shadow: ${props => props.disabled ? 'unset' : '0px 1px 2px 1px #b3b3b3'};
}
&:active {
box-shadow: ${props => props.disabled ? 'unset' : 'inset 0 0 10px #2f2f2f80'};
}
`;
export default class ButtonPrimary extends React.Component {
render() {
return (
<div>
<Button disabled={this.props.disabled}>{this.props.label}</Button>
</div>
)
}
}
Does anyone have any idea why?
In your parent component, you need to change disabled to be a boolean instead of a string.
storiesOf('Buttons')
.add('Primary', () => <Primary label="Default" disabled={false} ></Primary>)
Or in case if you need to use it as a string you need to specify your conditional
${props => props.disabled === 'true' ? '#EDEDED' : '#0076C0'};

Categories