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

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

Related

react styled-component nested item's props

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

Does a component created with styled-components destroy the component when its style changes, rather than just modifying the component's style?

I have created two boxes, one with the css module scheme and the other with styled-components. However, the transition will fail on the box created with styled-components, but not on the box created with the css modules scheme.
I have added a button to demonstrate the difference.
import React, { useState } from "react";
import styled from "styled-components";
import "./App.css";
const App = () => {
const [boxHeight, setBoxHeight] = useState(20);
const Box = styled.div`
border: 2px solid black;
height: ${boxHeight}px;
padding: 0 2rem;
width: 4rem;
transition: all 3s linear;
`;
return (
<div>
<div
className="box"
style={{ border: "2px solid black", height: `${boxHeight}px` }}
>
box 1
</div>
<Box>box 2</Box>
<button onClick={() => setBoxHeight(boxHeight + 100)}>+100</button>
</div>
);
};
/* App.css */
.box{
border: 2px solid black;
padding: 0 2rem;
width: 4rem;
transition: all 2s linear;
}
My guess is that when the style of a component created by styled-components changes, the component is destroyed and a new one is created with the changed style, rather than just modifying the component.
I wonder if there is any good solution to use transition through styled-components .
Thanks to my colleague, now I know that I should create the component created by styled-components outside of another component. If it is created inside the component, it will be recreated every time it is rendered.
import React, { useState } from "react";
import styled,{css} from "styled-components";
import "./App.css";
const Box = styled.div`
${props=>css`
height: ${props.height}px;
`}
border: 2px solid black;
padding: 0 2rem;
width: 4rem;
transition: all 3s linear;
`;
const App = () => {
const [boxHeight, setBoxHeight] = useState(20);
return (
<div>
<div
className="box"
style={{ border: "2px solid black", height: `${boxHeight}px` }}
>
box 1
</div>
<Box height={boxHeight}>box 2</Box>
<button onClick={() => setBoxHeight(boxHeight + 100)}>+100</button>
</div>
);
};
export default App;

How to extend style using emotion-styled?

I would like to pass style to two styled component using emotion.
I've tried like this, but ${commonStyle} doesn't inherit to styled.a and styled.span:
const commonStyle = css`
color: #000;
font-size: 1.6rem;
`;
const commonLink = styled.a`
${commonStyle}
`;
const whiteLink = styled.span`
${commonStyle}
color: #fff;
`;
How can I extend css using emotion?
I have came across a solution. Instead of inheriting commonStyle as the first property in commonLink and whiteLink, inherit it as the last property of the both commonLink and whiteLink.
Look at the code below, I am inheriting commonStyle styles into Child component and its working fine.
import styled from '#emotion/styled'
import {css} from '#emotion/css'
const commonStyle = css`
color: purple;
`
const Child = styled.div`
font-size: 1.6rem;
${commonStyle}
`
const Parent = styled.div`
${Child} {
color: green;
}
`
render(
<div>
<Parent>
<Child>Green</Child>
</Parent>
<Child>Purple</Child>
</div>
)

I have tried using styled component in my App. But the cursor moves out after I write city name

In the search area every time I type any letter the cursor moves out and I need to click the text area again to type the next letter. While using CSS it is working fine but in Styled components I am facing the issue. I think there is some issues with my styling but I am unable to debug. How can I fix this issue. Please help.
import React, { FC, useState, FormEvent } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { setAlert } from '../store/actions/alertActions';
import { getWeather, setLoading } from '../store/actions/weatherActions';
interface SearchProps {
title: string;
}
const Search: FC<SearchProps> = ({ title }) => {
const dispatch = useDispatch();
const [city, setCity] = useState('');
const changeHandler = (e: FormEvent<HTMLInputElement>) => {
setCity(e.currentTarget.value);
}
const submitHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if(city.trim() === '') {
return dispatch(setAlert('City is required!'));
}
dispatch(setLoading());
dispatch(getWeather(city));
setCity('');
}
const Header = styled.h1`
font-size: 40px;
font-family: 'sans-serif';
padding-top: 30px;
`;
const Input = styled.input`
font-size: 18px;
padding: 10px;
margin: 10px;
background: #b4e6df;
border: none;
border-radius: 3px;
::placeholder {
color: black;
}
`;
const Button = styled.button`
background-color: #10e2f1;
font-size: 20px;
color: white;
margin: 1em;
border: 3px;
padding: 0.25em 6em;
`;
return(
<>
<Header >{title}
<form onSubmit={submitHandler}>
<Input
type="text"
placeholder="Enter city name"
value={city}
onChange={changeHandler}
/>
<br/>
<Button >Search</Button>
</form>
</Header>
</>
);
}
export default Search;
Looks like the value is changing (or refreshing) whenever you make a change in the input as it is bind with state and also the state is updating on onChange event. Keep independent value instead of state variable in value attribute. something like this:
const defaultValue = ''; // or the default value you are getting from props
const [city, setCity] = useState(defaultValue);
---
---
<Input
type="text"
placeholder="Enter city name"
value={defaultValue}
onChange={changeHandler}
/>
Let me know if it works or not.

how to pass className to svg in styled components

I have been trying to style svg icon component via styled component and facing some issue but it's not working.The style i apply to close icon are not getting applied
import styled from 'styled-components'
import Close from './close.svg'
const CloseIcon = ({ className , ...props }) => <Close {...props} className={className} />
const styledCloseIcon = styled(CloseIcon)`
&.ui.modal {
.modal-icon-close {
width: 14px;
height: 24px;
fill: black;
top: 12px;
right: 14px;
}
}
`
export default styledCloseIcon
close.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13.627 12.213l9.9 9.9-1.414 1.414-9.9-9.9-9.9 9.9L.9 22.113l9.9-9.9-9.9-9.9L2.314.9l9.9 9.9 9.899-9.9 1.414 1.415-9.9 9.9z"/>
</svg>
This close icon is being used in semantic ui react modal
https://react.semantic-ui.com/modules/modal/#variations-close-icon
<Modal
size='small'
open={true}
closeIcon={<Close className='modal-icon-close'/>}
closeOnDimmerClick={false}
className={classNames(className)}
>
When you call styled(CloseIcon) you are already styling the SVG because the custom component accepts className prop so styled-components can inject the className and only renders the SVG icon.
Try omitting all the nested class names and directly apply the styles.
Here is an example,
import React from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import { ReactComponent as Close } from "./close.svg";
const CloseIcon = ({ className, ...props }) => (
<Close {...props} className={className} />
);
const StyledCloseIcon = styled(CloseIcon)`
width: 30px;
height: 30px;
fill: #ee4845;
`;
const App = () => {
return (
<div>
<StyledCloseIcon />
</div>
);
};
Working demo in codesandbox
Issues
I think there are a couple of issues
import Close from './close.svg'; isn't a valid react component
Unnest the class in the styled component
Solution
First create a proper SVG react component
const CloseIcon = ({ className, ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
{...props}
className={className}
>
<path d="M13.627 12.213l9.9 9.9-1.414 1.414-9.9-9.9-9.9 9.9L.9 22.113l9.9-9.9-9.9-9.9L2.314.9l9.9 9.9 9.899-9.9 1.414 1.415-9.9 9.9z" />
</svg>
);
Second make modal-icon-close a top-level class in the styled component
const StyledCloseIcon = styled(CloseIcon)`
&.modal-icon-close {
width: 14px;
height: 24px;
fill: black;
top: 12px;
right: 14px;
}
`;
What doesn't make much sense to me is why internalize the classname and CSS and then require the correct classname prop to be passed anyway? You could simplify the component by also specifying the className prop using .attrs
const StyledCloseIcon = styled(CloseIcon).attrs(() => ({
className: 'modal-icon-close',
}))`
&.modal-icon-close {
width: 14px;
height: 24px;
fill: black;
top: 12px;
right: 14px;
}
`;
Or just simply eliminate the classname altogether
const StyledCloseIcon = styled(CloseIcon)`
width: 14px;
height: 24px;
fill: black;
top: 12px;
right: 14px;
`;
here's how I assign className to SVG file:
import { ReactComponent as MyImage } from '../assets/img/myimage.svg'
<MyImage
className='myClassName'
fill='white'
/>
Index.css:
.hoverColor:hover {
fill: yellow !important;
opacity: 1 !important;
}

Resources