Styling nested components using styled components - reactjs

I've been using styled components for very little time.
At the moment I'm trying to do a style override on a nested element and I've having trouble understanding what I'm doing wrong.
So my struture is.
---------------------------Form.js---------------------------
import { FieldWrapper } from './FieldWrapper';
const Form = styled.form`
/** my form styles **/
`;
const StyledFieldWrapper = styled(FieldWrapper)`
/** my FieldWrapper styles **/
input {
/** input overrides **/
padding-bottom: 0.8rem;
height: 2rem;
line-height: 2rem;
box-sizing: content-box;
background-color: pink !important; // added just for visibility
}
`;
const MyForm = props => {
return (
<>
<Form>
<StyledFieldWrapper />
</Form>
</>
);
}
export { MyForm }
---------------------------FieldWrapper.js---------------------------
const Div = styled.div`
/** my div styles **/
`;
const Label = styled.label`
/** my label styles **/
`;
const Input = styled.input`
/** my input styles **/
`;
const FieldWrapper = props => {
return (
<Div>
<Label>
<Input />
</Label>
</Div>
);
}
export { FieldWrapper }
Now what I expect to happen was that the styles in FieldWrapper.js would be overriden by the StyledFieldWrapper element in Form.js, this however does not happen and I have no idea why. I have overrides like this in the past and in this project. Also StyledFieldWrapper does not contain only overrides, it also has style of its own and I can't even see those.
Has anyone had a similar issue?
NOTE: I have tried to use the solution in Styling Nested Components in Styled-Components with no success.

EDIT:
Since you want the styles to apply to a custom component, you also need to manually assign the className generated by styled-components to the top-level element of that component. Something like:
const FieldWrapper = props => {
return (
<Div className={props.className}>
<Label>
<Input />
</Label>
</Div>
);
}
The problem is likely related to CSS Specicifity, meaning that the original css styles defined in FieldWrapper has higher "importance" than the ones in Form. If you inspect your element you can probably see that both styles are applied, but the former has precendence over the latter.
A way to solve that is to either use the !important rule to each of the input styles defined in your Form component. Another would be to add a class to <Input /> and define your styles as myClass.input. Basically anything that would increase the specicifity of the rules you want to apply.
See the above link for more info of how to do that. Also check out Cascade and inheritance:
Once you understand the fact that source order matters, at some point you will run into a situation where you know that a rule comes later in the stylesheet, but an earlier, conflicting, rule is applied. This is because that earlier rule has a higher specificity — it is more specific, and therefore is being chosen by the browser as the one that should style the element.
As we saw earlier in this lesson, a class selector has more weight than an element selector, so the properties defined on the class will override those applied directly to the element.
Something to note here is that although we are thinking about selectors, and the rules that are applied to the thing they select, it isn't the entire rule which is overwritten, only the properties which are the same.
The amount of specificity a selector has is measured using four different values (or components), which can be thought of as thousands, hundreds, tens and ones — four single digits in four columns:
Thousands: Score one in this column if the declaration is inside a style attribute, aka inline styles. Such declarations don't have selectors, so their specificity is always simply 1000.
Hundreds: Score one in this column for each ID selector contained inside the overall selector.
Tens: Score one in this column for each class selector, attribute selector, or pseudo-class contained inside the overall selector.
Ones: Score one in this column for each element selector or pseudo-element contained inside the overall selector.
Here is an example from MDN:
/* specificity: 0101 */
#outer a {
background-color: red;
}
/* specificity: 0201 */
#outer #inner a {
background-color: blue;
}

Related

Styled Components - extending element styling and overriding properties

I'm using a pre-existing styled button component and the majority of the original styling, however the 'top' and 'right' properties need updating.
I have tried the following:
const StyledButton = styled(Button)`
right: -5px;
top: 40px;
`;
I thought this would extend the styling to the original component, however it doesn't seem to be feeding through. Any help would be much appreciated.
Often, in this case, it's simply a matter of the underlying component not passing on the className prop. I find that, with components that are going to be shared/re-used, it can be helpful in their root element to have {...props} to ensure people can override whatever they need to on that element.
e.g.
const Button: React.FC = ({ text, ...props }) => {
return <button {...props}>{text}</button>
}

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.

Preserve class name when wrapping element with styled-components

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.

React component theme using sass variables depending on React prop value

I cannot find better solution for theming. I have a sass-file with variables:
vars.scss:
$colors: (
dark: (
theme1: #0096dc,
theme2: #eb8200,
…
),
…
);
For now I pass theme prop to all components which should have some styling depending on which page user is viewing. In React I build classname like this:
<div className={styles[`button${theme ? `-${theme}` : ``}`]}>{children}</div>
and in sass I do:
#import 'vars';
.button{
/* basic styles */
font-size: 14px;
border: 1px solid;
#each $theme, $color in map-get($colors, dark) {
&-#{$theme} {
#extend .button;
border-color: $color;
color: $color;
}
}
}
But this is absolutely inconvenient, because I need to place such code every time I need to style element depending on theme.
There are solutions how to load different files with variables for theming in webpack, but I receive theme prop from redux, so webpack doesn't have access to that prop.
As well as, I cannot find solution to pass property from react component to sass. Only opposite.
No way to use variables in imports in sass. Still not implemented.
Unfortunately, Cannot use CSS-in-JS approach in current project.
Please help to find better approach.
I would make it a stateless functional component.
function BrightBox(props){
<div className={styles[`button${theme ? `-${theme}` : ``}`]}>
{props.children}
</div>
}
Then import it and use it. There is no need to pass theme around.
{user.notice ?
<BrightBox>
there are {user.notice.issues.size} outstanding issues!
</BrightBox>
: null
}

Set text input placeholder color in reactjs

When using ordinary CSS if you want to style your place holder you use these css selectors :
::-webkit-input-placeholder {
color: red;
}
But I can't figure out how to apply these type of styles in react inline styles.
Simply give your "input" tag an "id" or a "class" and put your css style in App.css inside src. e.g
//App.css or external stylesheet
#inputID::placeholder {
color: #ff0000;
opacity: 1;
}
//your jsx code
<input type="text" id="inputID" placeholder="Your text here" />
That actually worked for me.
You can't use ::-webkit-inline-placeholder inline.
It is a pseudo-element that (much like e.g. :hover) can only be used in your stylesheet:
The non-standard proprietary ::-webkit-input-placeholder pseudo-element represents the placeholder text of a form element.
Source
Instead, assign a class to the React component via the className property and apply the style to this class.
You could try to use radium
var Radium = require('radium');
var React = require('react');
var color = require('color');
#Radium
class Button extends React.Component {
static propTypes = {
kind: React.PropTypes.oneOf(['primary', 'warning']).isRequired
};
render() {
// Radium extends the style attribute to accept an array. It will merge
// the styles in order. We use this feature here to apply the primary
// or warning styles depending on the value of the `kind` prop. Since its
// all just JavaScript, you can use whatever logic you want to decide which
// styles are applied (props, state, context, etc).
return (
<button
style={[
styles.base,
styles[this.props.kind]
]}>
{this.props.children}
</button>
);
}
}
// You can create your style objects dynamically or share them for
// every instance of the component.
var styles = {
base: {
color: '#fff',
// Adding interactive state couldn't be easier! Add a special key to your
// style object (:hover, :focus, :active, or #media) with the additional rules.
':hover': {
background: color('#0074d9').lighten(0.2).hexString()
},
'::-webkit-input-placeholder' {
color: red;
}
},
primary: {
background: '#0074D9'
},
warning: {
background: '#FF4136'
}
};
Do not use ::-webkit-inline-placeholder inline and Radium for one placeholder.
I suggest go to your index.css
input.yourclassname::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: white;
opacity: 1; /* Firefox */
}
My approach is to simply apply different styles to the entire <input /> component based on whether or not the value is empty. No need to install a new dependency, and no need to use a stylesheet which seems to be the point of the original question.
var inputStyles = {
border: '1px solid #cbcbcb',
color: '#525252',
};
var placeholderStyles = {
...inputStyles,
color: '#999999',
};
<input
type="text"
placeholder="enter text here"
value={myValue}
style={myValue ? inputStyles : placeholderStyles}
/>
For me, I use Radium's Style component. Here's what you can do in ES6 syntax:
import React, { Component } from 'react'
import Radium, { Style } from 'radium'
class Form extends Component {
render() {
return (<div>
<Style scopeSelector='.myClass' rules={{
'::-webkit-input-placeholder': {
color: '#929498'
}}} />
<input className='myClass' type='text' placeholder='type here' />
</div>
}
}
export default Radium(Form)
Use Template Literals i.e. Backward quotes(``) for adding your pseudo-element.
const inputFieldStyle = `
.inputField::-webkit-input-placeholder{
color: red;
}`
Using class is important thing so make sure you used class before pseudo-element.
Then you can use tag where you pass above style like below:
const reactFunctionalComponent = (props) => {
...
return(
<>
<style>
{inputFieldStyle}
</style>
...
</>
)
}
Here is a method that provides pseudo selector functionality in React, without relying on a third party library, in about 20 lines of code.
This is an extension of RohitSawai's answer that injects a <style> tag, but in a more encapsulated and reusable fashion.
Define this function once in your project:
/** Returns a unique className and injectStyle function that can be used to
style pseudo elements. Apply the className to the desired element and render
injectStyle() nearby. The pseudo selector, e.g. ::-webkit-input-placeholder,
will be appended to the unique className, styling a pseudo element directly,
or a descendant pseudo element, as determined by the selector. */
const pseudo = (pseudoSelector, style) => {
const className = `pseudo-${Math.floor(Math.random() * 1000000)}`
// simple encoding of style dictionary to CSS
// for simplicity, assumes that keys are already in slug case and units have
// been added, unlike React.CSSProperties
const styleCSS =
'{' +
Object.entries(style)
.map(([name, value]) => `${name}: ${value};`)
.join('') +
'}'
return {
className,
injectStyle: () => (
<style>
{`.${className}${pseudoSelector} ${styleCSS}`}
</style>
)
}
}
And use it like this:
const MyComponent = () => {
const placeholder = pseudo('::-webkit-input-placeholder', { color: 'red' })
return (<div>
{placeholder.injectStyle()}
<input className={placeholder.className} placeholder="This placeholder is red" />
</div>)
}

Resources