How to EXTEND imported component styled components - reactjs

I cannot extend my imported component. I was looking into styled components docs and find that in v4+ should works prop "as", but it doesnt.
COMPONENT:
type Props = {
padding: string,
justify: string
}
const FlexContainer = styled.div<Props>`
padding: ${props => props.padding};
display: flex;
flex-wrap: wrap;
justify-content: ${props => props.justify};
`
export const Flexcontainer: React.FC<Props> = props =>{
return (
<FlexContainer padding={props.padding} justify={props.justify}>
{props.children}
</FlexContainer>
)
}
EXTENDED STYLE:
import { Flexcontainer } from '../../reusable/FlexContainer';
const FlexContainerExtended = styled.div`
color: red;
`
USE:
<FlexContainerExtended
padding={null}
justify={"flex-start"}
as={Flexcontainer}>

You just have to add a prop className to your Flexcontainer component like this:
export const Flexcontainer: React.FC<Props> = props =>{
return (
<FlexContainer className={props.className} padding={props.padding} justify={props.justify} >
{props.children}
</FlexContainer>
)}
To override styles, styled-components passes a className as props to the overrided component, it's why

You can just pass the base component to the styled function to override it.
type Props = {
padding: string,
justify: string
}
const FlexContainer = styled.div<Props>`
padding: ${props => props.padding};
display: flex;
flex-wrap: wrap;
justify-content: ${props => props.justify};
`
const FlexContainerExtended = styled(FlexContainer)`
color: red;
`
export const Flexcontainer: React.FC<Props> = props =>{
return (
<FlexContainer padding={props.padding} justify={props.justify}>
{props.children}
</FlexContainer>
)
}
// And use it like this
<FlexContainerExtended
padding={null}
justify={"flex-start"}/>

I know this question was asked a long time ago, but I leave here the solution I found for future visitors.
Base component definition
import React from 'react'
import styled from 'styled-components'
const ContainerWrapper = styled.div`
width: 100%;
max-width: 1200px;
padding-left: 5%;
padding-right: 5%;
margin-left: auto;
margin-right: auto;
`
export default function Container({ children, ...props }) {
return (
<ContainerWrapper {...props}>
{children}
</ContainerWrapper>
)
}
Extended component definition
Obs.: note that the extended component is an article, while the base component is a div, and so far they have no relationship.
Also note that the base component (Container) has been imported but not yet used.
import React from 'react'
import styled from 'styled-components'
import Container from '../../common/Container'
const HeroWrapper = styled.article`
height: 100vh;
padding-top: 74px;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
background-attachment: fixed;
`
Invoking componentes
Now, in the same file where I declared the extended component, I just call the base component, informing the name of the extended component in the as attribute.
export default function PostsWrapper() {
return (
<Container as={HeroWrapper}>
{/* ... */}
</Container>
)
}

Related

React Styled Components styles do not apply properly

I have problem with understanding how cascade style sheet algorithm works with React library styled-component. I have created Todo element with 3 fields, and I gave each its own class , like TodoTitle, TodoDescription and TodoAction - so that div with different class will have diferent width. Yet width property does not apply to class, even though it is flex. Other properties like red backgound did aplied.
import React from 'react';
import { Todo } from '../../types/Todo';
import { StyledTodoItem } from './TodoItem.styles';
type Props = {
todo: Todo;
index: number;
};
export const TodoItem: React.FC<Props> = ({ todo, index }) => {
return (
<StyledTodoItem bg={index % 2 === 0 ? 'even' : 'odd'}>
<div className="todoTitle">
<h1>{todo.title}</h1>
</div>
<div className="todoDescription">
<h1>{todo.text}</h1>
</div>
<div className="todoAction">
<button type="button">Hello</button>
</div>
</StyledTodoItem>
);
};
import styled from 'styled-components';
type Props = {
bg: 'even' | 'odd';
};
export const StyledTodoItem = styled.div<Props>`
width: 100%;
display: flex;
background: ${({ bg }) => (bg === 'odd' ? 'white' : '#ededed')};
height: ${({ theme }) => theme.todoTable.elementMinHeight};
border-right: 1px solid ${({ theme }) => theme.palette.borderColor};
.todoTitle {
display: flex;
justify-content: center;
padding: 10px 20px;
width: 150px;
background: red;
}
`;
If , instead of .todoTitle I paste div , width will be applied, but for every element.
Please, explain how to fix this and any similar problem like this that might arise.
Thanks in advance.
Thanks for comments. So I checked out few tutorials and it seems that best practice is to avaid as possible html tags and to change them for custom styled components as provided in example. Also nested components do lead to lots of bugs, or so it seems.
return (
<>
<TodoItemWrapper bg={index % 2 === 0 ? 'even' : 'odd'}>
<TodoTitle>
<TextTitle>{todo.title}</TextTitle>
</TodoTitle>
<TodoDescr>
<TextDescr>{todo.text}</TextDescr>
</TodoDescr>
<TodoAction>
<Button>View</Button>
<Button>Edit</Button>
<Button>Delete</Button>
</TodoAction>
</TodoItemWrapper>
)
export const TodoItemWrapper = styled.div`
width: 100%;
display: flex;
background: ${({ bg }) => (bg === 'odd' ? 'white' : '#ecfcff')};
height: fit-content;
`;
export const TodoText = styled.div`
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 20px;
`;
export const TodoTitle = styled(TodoText)`
width: ${({ theme }) => theme.todoTable.title};
border-right: 1px solid ${({ theme }) => theme.palette.borderColor};
`;

Styled-components: Styles are not applied when trying to style already styled component

I'm trying to style my component which was styled already. But styles in the new rules are not applied in output CSS.
Can I style component that I already styled?
Thanks you in advance for your help.
EDIT: Add rest of LanugageChooser definition
// COMPONENT THAT I'M TRYING TO STYLE
const LanguageChooser = () => {
const Container = styled.div`
display: flex;
align-items: center;
height: 36px;
& > div:not(:last-child) {
margin-right: 5px;
}
`;
return (
<Container>
<Flag { ...languages.pl }/>
<Flag { ...languages.en }/>
</Container>
)
}
const Flag = ({ flag, language }) => {
const { i18n } = useTranslation();
const Button = styled.div`
cursor: pointer;
font-size: 24px;
transition: .2s all;
&:hover {
font-size: 36px;
}
`;
return (
<Button onClick={ () => i18n.changeLanguage(language) }>{ flag }</Button>
)
}
// TRYING ADD MARGIN LEFT, BUT THERE IS NO RESULT.
// ANY OTHER CSS PROPERTY ARE NOT APPLYING
const Navbar = ({ color }) => {
...
const StyledLanguageChooser = styled(LanguageChooser)`
margin-left: auto;
`;
const Nav = styled.nav`
display: flex;
align-content:center;
background: ${ color };
padding: 2px 3px;
`;
return (
<Nav className="navbar">
<StyledNavLink to="/home">...</StyledNavLink>
<StyledNavLink to="/maps">...</StyledNavLink>
<StyledNavLink to="/charts">...</StyledNavLink>
<StyledLanguageChooser/>
</Nav>
)
}
First, move the styled component outside of function scope or your style will reset on every render and you will get heavy performance issues.
Secondly, in order to apply styles, you need to pass className property.
See Styling normal React components
If you use the styled(MyComponent) notation and MyComponent does not render the passed-in className prop, then no styles will be applied.
const Container = styled.div`
display: flex;
align-items: center;
height: 36px;
& > div:not(:last-child) {
margin-right: 5px;
}
`;
const LanguageChooser = ({ className }) => {
return (
<Container className={className}>
<Flag {...languages.pl} />
<Flag {...languages.en} />
</Container>
);
};
const Button = styled.div`
cursor: pointer;
font-size: 24px;
transition: 0.2s all;
&:hover {
font-size: 36px;
}
`;
const Flag = ({ flag, language }) => {
const { i18n } = useTranslation();
return <Button onClick={() => i18n.changeLanguage(language)}>{flag}</Button>;
};

React styled components with themeContext

I have the following React Header component in which I'm trying to get a theme value from the ThemeContext.Consumer and pass it into my NavListItem. It's currently working (sort of) but I'd like to only pass it once and have it applied globally. Instead of having to write theme={theme.typography.navigation} each time I use the styled component?
import React from "react"
import { Link } from "gatsby"
import PropTypes from "prop-types"
import styled from "styled-components"
import ThemeContext from "../context/ThemeContext"
const Main = styled.header`
height: 70px;
background-color: white;
position: fixed;
width: 100%;
left: 0;
`
const NavWrapper = styled.div`
position: relative;
`
const NavListLeft = styled.ul`
color: black;
position: absolute;
top: 25px;
left: 40px;
list-style: none;
margin: 0;
padding: 0;
`
const NavListItem = styled.li`
display: inline-block;
margin: 0 10px;
font-family: ${props => props.theme};
font-size: 16px;
`
const Header = ({ siteTitle }) => (
<ThemeContext.Consumer>
{theme => (
<Main>
<NavWrapper>
<NavListLeft>
<NavListItem theme={theme.typography.navigation}>
<Link style={{color: 'black'}}>Women</Link>
</NavListItem>
<NavListItem theme={theme.typography.navigation}>Men</NavListItem>
<NavListItem theme={theme.typography.navigation}>Designers</NavListItem>
<NavListItem theme={theme.typography.navigation}>Collection</NavListItem>
<NavListItem theme={theme.typography.navigation}>Sale</NavListItem>
</NavListLeft>
</NavWrapper>
</Main>
)}
</ThemeContext.Consumer>
)
Header.propTypes = {
siteTitle: PropTypes.string,
}
Header.defaultProps = {
siteTitle: ``,
}
export default Header
You could create a NavListItem that consumes your theme.
const ThemedNavListItem = styled.li`
display: inline-block;
margin: 0 10px;
font-family: ${props => props.theme.typography.navigation};
font-size: 16px;
`
const NavListItem = props => (
<ThemeContext.Consumer>
{theme => <ThemedNavListItem {...props} theme={theme} />}
</ThemeContext.Consumer>
);
const Header = ({ siteTitle }) => (
<Main>
<NavWrapper>
<NavListLeft>
<NavListItem>
<Link style={{color: 'black'}}>Women</Link>
</NavListItem>
<NavListItem>Men</NavListItem>
<NavListItem>Designers</NavListItem>
<NavListItem>Collection</NavListItem>
<NavListItem>Sale</NavListItem>
</NavListLeft>
</NavWrapper>
</Main>
);
A simpler solution using styled-components ThemeProvider.
A helper component for theming. Injects the theme into all styled
components anywhere beneath it in the component tree, via the context
API.
const NavListItem = styled.li`
display: inline-block;
margin: 0 10px;
font-family: ${props => props.theme.typography.navigation};
font-size: 16px;
`
const Header = ({ siteTitle }) => (
<ThemeProvider theme={theme}>
<Main>
<NavWrapper>
<NavListLeft>
<NavListItem>
<Link style={{color: 'black'}}>Women</Link>
</NavListItem>
<NavListItem>Men</NavListItem>
<NavListItem>Designers</NavListItem>
<NavListItem>Collection</NavListItem>
<NavListItem>Sale</NavListItem>
</NavListLeft>
</NavWrapper>
</Main>
</ThemeProvider>
);

styled-components causing unexpected behaviour when rendering props

I have a WhiteBox react component which simply renders a white box with some styles.
I have a SmallBox react component which simply uses WhiteBox to render a more specific box.
I have a Content react component which renders three SmallBox boxes which does what it's supposed to do (renders three white boxes).
However when I tried to add a text as a props from the parent, the white box is aligned with some unexpected margin from top.
NOTE: when I simply put "This is a text" the css is okay, but when I send "this is a text" as props.text, the whitebox is rendered with extra margin from top.
I Use styled-components and react as I said.
I've tried to console.log the text, and everything seems to be okay. I also tried to switch the props.children or props.text and it does not work
-----------------White Box Component ----------------------
import styled from "styled-components";
const StyledBox = styled.div`
display: inline-block;
width: ${props => props.width}px;
height: ${props => props.height}px;
margin-right: ${props => props.marginRight}px;
margin-left: 100px;
background-color: white;
border-radius: 5px;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
color: #646777;
padding: 10px;
`;
const WhiteBox = props => {
return (
<StyledBox
width={props.width}
height={props.height}
marginRight={props.marginRight}
>
{props.text} // if I change this to simply "this is a text" it works well. somehow the props.text is causing problems.
</StyledBox>
);
};
export default WhiteBox;```
-----------------Small Box Component ----------------------
import React from "react";
import styled from "styled-components";
import WhiteBox from "../whitebox/white-box";
const SmallBox = props => {
return (
<WhiteBox width={320} height={130} marginRight={70} marginLeft={70} text={props.text}>
</WhiteBox>
);
};
export default SmallBox;
-----------------Content Component ----------------------
import React, { Component } from "react";
import SmallBox from "./smallbox/small-box";
import styled from "styled-components";
const StyledContent = styled.div`
position: absolute;
left: 320px;
top: 80px;
width: 100%;
height: 100%;
background-color: #f1f3f7;
`;
class Content extends Component {
render() {
return (
<>
<StyledContent>
<SmallBox text="this text is great" /> // causing problem
<SmallBox />
<SmallBox />
</StyledContent>
</>
);
}
}
export default Content;
The issue has to do with how many lines are rendered. The longer the text in the props, the more the lines rendered.
One solution would be to change the display property for WhiteBox:
const StyledBox = styled.div`
display: inline-flex;
....
`;
Another solution would be to override the default style vertical-align: baseline, simply add vertical-align: top;
const StyledBox = styled.div`
display: inline-block;
....
vertical-align: top;
`;

Styled-component not taking props

I'm trying to pass props to my styled component. I am using the styled-component npm dependency.
import React, { Component } from 'react';
import styled from 'styled-components';
class WidgetContainer extends Component {
render() {
return (
<Wrapper name='widget-container'>
</Wrapper>
);
}
}
const mapStateToProps = state => {
return {
user: state.user,
bannerStatus: true
};
};
export default withRouter(connect(mapStateToProps)(WidgetContainer));
const Wrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
min-height: 100vh;
margin: auto;
align-items: center;
justify-content: flex-start;
background: -webkit-linear-gradient(top, rgba(162, 209, 234, 1) 0%, rgba(56, 83, 132, 1) 100%);
padding-top: ${(props) => {
console.log(props.bannerStatus)
console.log('DATA')
return props.user ? '160px' : '126px'}};
overflow-y: auto;
padding-bottom: 40px;
#media(max-width: 768px) {
padding-top: 126px;
padding-bottom: 5px;
}
`;
The console.log you can see returns undefined. Even though the value is hard-coded. The ternary operator doesn't work as a result.
Here are the docs: https://www.styled-components.com/docs/basics#adapting-based-on-props.
You are mapping state to props in only the WidgetContainer. Your Wrapper component is not being passed any props.
Try
<Wrapper bannerStatus={props.bannerStatus} name='widget-container' />
OR
<Wrapper user={props.user} bannerStatus={props.bannerStatus} name='widget-container' />

Resources