Preserve class name when wrapping element with styled-components - reactjs

I'm leveraging the Bootstrap grid so I'm creating components that render elements that include a class:
const Row = props => (
<div className="row" {...props}>
{props.children}
</div>
);
Now, at times I want my row to be padded, so I attempted to wrap my Row component:
const PaddedRow = styled(Row)`
padding: 20px;
`;
This applies the padding, but when the div is rendered, it is without the class.
Chrome's React plugin shows the following:
<Styled(Row)>
<Row className=".sc-cHds hQgwd">
<div className=".sc-cHds hQgwd"> // No "row" class
Is it not possible to preserve the class name for the wrapped component?

What you're doing with {...props} is spreading the passed-in props. styled-components passes the generated className via these props, and because you apply the spread after your className property it overrides the existing class.
If you were to write out manually what your code does in the end this is what it'd look like:
<div className="row" className="sc-cHds">
This makes it apparent what the issue is: the second className declaration is overriding the first one! The solution if I were to stick strictly to your code would be to concatenate the passed in class manually:
<div {...props} className={`row ${props.className}`}>
This works, however it's not idiomatic usage of styled-components. Rather than wrapping the component with styled(Row) we recommend creating a styled component for that <div />:
const RowWrapper = styled.div`
padding: 20px;
`
const Row = props => (
<RowWrapper className="row">
{props.children}
</RowWrapper>
);
This is the way styled-components is intended to be used, it will also automatically concatenate your Bootstrap class with the automatically generated one.

Related

How to use styled-component CSS props with Typescript

I map over an array of colors, which is defined in the database. I want to pass this colors as background for the created divs. Like I am used to, the console shows me, that the colors out of the array are passed in as prop. But using the props in styled components not works in typescript. I tried the following, what I have found in the net:
import * as types from 'styled-components/cssprop'
import type {} from 'styled-components/cssprop';
/// <reference types="styled-components/cssprop" />
I only passed this variations into my file.
The both snippets:
<ColorHolder>
{item.colors.map((color)=>(
<div color={color}></div>
))}
</ColorHolder>
css:
& div{
width:20px;
height:20px;
border-radius:50%;
background:${props=>props.color};
}
As far as I understand your code, you don't need to use any libraries.
Here is the working example, where colors in the array you fetched from backend
<div>
{
colors.map(color=>(
<div style={{backgroundColor: color, height: "50px",width: "50px"}}>
.
</div>))
}
</div>
Here's full example - codesandbox.io
Only styled components can receive props for this style adaptation technique.
Therefore in your case simply create a quick styled div:
const StyledDiv = styled.div<{ color: string }>`
background: ${props => props.color};
`;
<ColorHolder>
{item.colors.map((color) => (
<StyledDiv color={color}></StyledDiv>
))}
</ColorHolder>

Why color appears as HTML attribute on a div?

This code works perfectly, it creates CSS classes with the corresponding color and applies the classes dynamically:
const Div = styled.div`
color: ${({ color }) => color};
`;
export default function Email() {
const [color, changeColor] = useState();
return (
<>
<Div color={color}>Email</Div>
{['red', 'palevioletred', 'blue', 'yellow'].map(x => <button onClick={() => changeColor(x)}>{x}</button>)}
</>
)
}
However, the color attribute appears in the DOM on the div element:
<div class="sc-crHlIS jxntNS" color="yellow">Email</div>
When I change the color prop name to something else like divColor it doesn't show as a DOM attribute. I understand that props appear as HTML attributes when they have the same name as a native HTML attribute, but color should not because it's not a valid HTML attribute.
Why this behavior?
Styled components has a set of HTML attributes that it allows to be passed down to the resulting element. The color attribute is not technically valid for a div element, however it is a HTML attribute, and this is the criteria it follows.
From the docs:
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.
The documentation does not explicitly state that they accept any attribute on any element, however it seems to be the case given this example:
<Div color="red" foo="foo" value="value" name="name" bar="bar">My div</Div>
Results in:
<div color="red" value="value" name="name">My div</div>
Notice how the non HTML attributes are removed, but the others remain.

css-loader css modules rules of nested component

Lets say I use scss modules in the following way
A.jsx:
import styles from './A.module.scss';
import B from '../B/B.jsx';
const A = props => {
return <div className={styles.a}>
<B/>
</div>;
};
export default A;
B.jsx:
import './B.scss';
const B = props => {
return <div className="b">
</div>;
};
export default B;
and inside A.module.scss I override B's css rules as follows:
.a {
.b {
width: 500;
height: 500;
background-color: red;
}
}
then the styles I override for the B instance inside A are not passes to it, obviously because it's using css modules. How can I make the css rules of B applies only in A and not globally?
I thought maybe I have to pass custom className props for every component I want to achieve this, so for this instance pass custom className prop to B and pass it like so:
<B className={styles.b}/>
and in B.jsx just apply the custom class to the component.
Not my favourite solution, would rather not adding custom class but use just custom selector .a .b as this defines the relationship of every B which is under A I want to target, but I'm wondering if there's a simpler solution like I'm looking for?
Also I'm not sure the className prop is a viable solution for when B has several nodes nested in it and I'm trying to target a nested node inside it with my css rules. I can't just have for every component I have a className prop for every node in it.
Here's an example illustrating this situation:
B.jsx:
import './B.scss';
const B = props => {
return <div className="b">
<div className"b-inner1">
<div className"b-inner2">
<div className"b-inner3">
</div>
</div>
</div>
</div>;
};
export default B;
Here for example I wish to style the node with the className b-inner3 which might be just a node that is there for helping with the basic layout of the B's component and I do not wish to be able users of this component pass className to this node.
Each component (like any other function) has some abstractions over implementation details, it also has an API, this API is a contract between you and the consumer of your component.
If you are not exposing a className or style prop, that means you are basically saying "this component will not guaranty to always keep the same structure or public class names in the DOM". If anyone will override your classes, he/she are taking a risk and may face a breaking change.
If you do want to give the consumers the ability to override or add styling, you have 2 (or 3) main options as i see it:
Accept style and className props for each node you want to allow
styling:
const MyComponent = (props) => (
<div className={`${styles.myStyle1} ${props.publicStyle1}`}>
<div className={`${styles.myStyle2} ${props.publicStyle2}`}>
<button
className={`${styles.button}
${props.publicStyleButton}`}
>
Click Me
</button>
</div>
</div>
);
Yes, this can get messy and may look like a confusing API to
consume and maintain.
Expose hardcoded class names:
const MyComponent = (props) => (
<div className={`${styles.myStyle1} public-style-1`}>
<div className={`${styles.myStyle2} public-style-2`}>
<button
className={`${styles.button} public-style-button`}
>
Click Me
</button>
</div>
</div>
);
This way, your contract says you will always keep those public class names (or a breaking change version)
Another approach which is not directly related to styling, usually
referred as "Compound Components", you can mix it with one of
the approaches above:
const MyButton = ({ text, onClick, ...rest }) => (
<button
onClick={onClick}
className={`${styles.button}`}
{...rest}
>
{text}
</button>
)
const MyComponent = (props) => (
<div className={`${styles.myStyle1} public-style-1`}>
{children}
</div>
);
MyComponent.Button = MyButton;
/* usage... */
<MyComponent>
<MyComponent.Button
text="Click Me"
className="consumer-class"
style={{color: 'green'}}
/>
</MyComponent>
This will let you break your component into smaller pieces and give
more control to your consumers.

Emotion: Using both a class and the "css" method in "className" prop

How can both a class plus additional CSS be applied when Emotion is used with the className prop in React?
For example, how can one add a class myClass to the following?
import {css} from 'emotion'
<div className={css`color: red`}>
I've never used Emotion but it looks like you can simply add your class and another template string around the css function.
<div className={`myClass ${css`color: red`}`}>
I tested this with one of their inline editors on a page in their Introduction then checked the markup. Seemed to work.
You can use composes with regular classes or classes from a css module
<div className={css`
composes: ${'some-class'};
color: red;` >
May be this can help you if you are still stuck click here
emotion v11 answer
import { css, ClassNames } from '#emotion/react'
const orange = css`color:orange;`
const bold = css`font-weight:bold;`
render(
<>
Using ClassNames
<br/>
<ClassNames>
{({ css, cx, theme }) => (
<>
<div className={`case1 ${css`color:red;`}`}>Case1</div>
<div className={cx('case2', css`color:green;`)}>Case2</div>
<div className={cx('case3', css`${orange}`)}>Case3</div>
<div className="Case4" css={orange}>Case4</div>
<div className="Case6" css={[orange, bold]}>Case5</div>
<div className={cx('case6', orange)}>Case6 (not working)</div>
</>
)}
</ClassNames>
<br/>
without ClassNames
<br/>
<>
<div className={`raw1 ${css`color:red;`}`}>Raw1 (now working)</div>
<div className="raw2" css={orange}>Case4</div>
<div className="raw3" css={[orange, bold,'abc']}>Case5 (no abc)</div>
</>
</>
)

Concatenating props and static values in style={{}} in React

I have a component I am passing a height as props so it can be used at page level like so (minified code)
<Component style={{height}} />
Component.propTypes = {
height: PropTypes.string, //make sure it's a string
}
Component.defaultProps = {
height: "100%", //100% otherwise defined
}
This can be used later as
<Component height="100%"/>
<Component height="50%"/>
<Component height="20%"/>
...
And renders as
<div class="component-blah-blah" styles="height: 100%;"></div>
I want to add overflow-x: hidden to the party but as a default and non-changeable prop. So that regardless of how they use the styles prop, it will always carry out the overflow-x I defaulted. Like so:
<Component height="100%"/>
<div class="component-blah-blah" styles="height: 100%; overflow-x:hidden"></div>
<Component height="50%"/>
<div class="component-blah-blah" styles="height: 50%; overflow-x:hidden"></div>
<Component height="20%"/>
<div class="component-blah-blah" styles="height: 20%; overflow-x:hidden"></div>
I know I can concatenate classes in string and props like this using the --> ` <-- and the $ sign, but not with a double bracket as style requires.
I'm looking for the syntax for something like this
className='${classes.myPropClass} myCSSclass'
which renders as class="myPropClass myCSSclass" , but for inline styles, not classes...and I can't assing overflow to a class. For other complicated reasons
The style prop takes an object. The inner brackets {} are just the syntax for creating an object inline. So just add the desired property to that object in the render function:
const Component = ({height}) => (
<div class="component-blah-blah" styles={{height, overflowX: 'hidden'}}></div>
);
The example code you've given doesn't seem to match the description you wrote along with it. You said (emphasis, mine):
So that regardless of how they use the styles prop, it will always carry out the overflow-x I defaulted.
But the code you've put here doesn't show there being a style prop. It just shows there being a height prop. If the user of this component is only given a height prop to use, there's no way that value could ever overwrite your overflowX style property.
But... if you're trying to allow the consumer to pass in their own style object in the props, and then ensure that they can't overwrite your desire to implement an overflowX feature, you should use a spread operator to effectively concatenate the user's style, with your style, while keeping your style from being overwritten. It would look something like this:
class App extends Component {
render() {
const styleFromProps = { display: 'none' };
return (
<p
style={{
...styleFromProps,
display: 'inherit',
}}
>
Is this displayed???
</p>
);
}
}
render(<App />, document.getElementById('root'));
Here is a live example:
https://stackblitz.com/edit/react-spread-operators-in-styles
Notice in this example that the styleFromProps object has a display value of none. But the contents of the <p> tag still display. Because the hardcoded value for display is listed in the style object after the display value that's passed in. In CSS, if an attribute is declared twice, the last one "wins".
So if you're allowing the user to pass in a style object as a prop, but you want to ensure that they don't overwrite some of the "critical" styles that you're using, you can do it in this way.

Resources