How to extend component style in styled-components - javascript

I have a basic ElipseDetail component that looks like the image below
<ElipseDetail text="1.07" />
When I am using the component everything works as expected.
But now I want to reuse the component in another place but add an extension to the Text component style
How can I achieve that with styled-components and reuse the component but change the Text which is a child?
import React, { ReactElement } from "react";
import styled from "styled-components";
interface Props {
text: string;
children?: React.ReactNode;
}
export const Container = styled.div`
padding: 4px 12px;
border-radius: 30px;
background-color: #eaeef2;
display: inline-block;
`;
export const Text = styled.p`
font-size: 12.5px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
letter-spacing: 0.63px;
color: #687c97;
`;
export default function ElipseDetail({ text, children }: Props): ReactElement {
return (
<Container>
<Text>{text}</Text>
{children}
</Container>
);
}

Since ElipseDetails is not a styled component, but calls to a styled one, you can do something like:
function ElipseDetail({ text, children }: Props): ReactElement {
return (
<Container>
<Text>{text}</Text>
{children}
</Container>
);
}
ElipseDetail.Styled = Container;
export default ElipseDetail
And then, in a different component, you can change it like so:
const StyledElipseDetail = styled(ElipseDetail.Styled)`
${Text} {
//
}
`;
...
return <StyledElipseDetailed>...</StyledElipseDetail>
PS - I have taken this approach from an older question of mine which I found quite useful.

I would suggest to create a container and if Text is in that container then add different properties. & here means this class name so it evaluates to Text so when text would be in ElipseContainer then it will behave in different way depending on your use case. Here if it's wrapped with ElipseContainer then colour of Text is green and it's red otherwise.
Here is sandbox link :
sandobx
const ElipseContainer = styled.div``;
const Text = styled.p`
color: red;
${ElipseContainer} & {
color: green;
}
`;
const App =() =>
<ElipseContainer>
<ElipseDetail text="word2" />
</ElipseContainer>

Related

Unable to apply styles to styled-component element in Reactjs

I'm trying to wrap a component with a styled-component div and apply some styles. However, for some reason, the styles aren't being applied, even though the ostensibly correct wrapper that is expected to be rendered is being rendred.
CodeSandbox
What am I doing wrong here?
You just forgot to pass the className props, see here:
const Component = ({ className }) => (
<div className={className}>Hello World!</div>
);
https://codesandbox.io/s/optimistic-lumiere-cq8gt?file=/src/App.js
I hope I helped you. Have a good day.
EDIT: Check the docs here
You need to container Component with styled.{tag} or styled(Component)
And then use that Container component to wrap main component.
import React from "react";
import "./styles.css";
import styled from "styled-components";
export default () => <Component />;
const Component = () => <StyledDiv>Hello World!</StyledDiv>;
const StyledDiv = styled.div`
font-size: 10rem;
color: red;
display: flex;
justify-content: center;
align-items: center;
`;
Check here: https://codesandbox.io/s/dank-hill-h8rbc?file=/src/App.js:128-189

Assign styled-component CSS property based on props passed to component

I have two components TextField and Label.
The TextField is passing the prop req to the Label. I want to modify the styled-component based on the req prop being passed in. Here is my current code that is not working.
No errors are being reported to the console.
TextField.js
import React, {Component} from 'react';
import styled from 'styled-components';
import Label from '../Label/Label';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
margin: 16px 8px 8px 8px;
`;
const Input = styled.input`
border-bottom: 1px solid rgba(0, 0, 0, .23);
&:focus {
border-bottom: 1px solid #2196f3;
}
`;
class TextField extends Component {
render() {
const {
label,
req = true,
} = this.props;
return (
<Wrapper>
<Label req={req} text={label}/>
<Input type={'textfield'}/>
</Wrapper>
);
}
}
export default TextField;
Label.js
import React, {Component} from 'react';
import styled from 'styled-components';
const LabelBase = styled.label`
color: rgba(0, 0, 0, .54);
font-size: 1rem;
line-height: 1;
&:after {
content: ${props => props.req ? '*' : ''};
}
`;
class Label extends Component {
render() {
const {
req,
text,
} = this.props;
return (
<LabelBase req={req}>{text}</LabelBase>
);
}
}
export default Label;
You say you want to style the component based on the ref prop, but it seems that you're using that prop as a boolean to add text, not styles so I just went with a simplified solution for that since psuedo-selectors like :after aren't supported in React's JS styles. There are other ways around that if need be, but I think you can just do the following. However, I've included a way to pass styles to the child component as well for your reference:
class Label extends React.Component {
render() {
const {
req,
text,
moreStyles
} = this.props;
const styles = {
"color": "rgba(0, 0, 0, .54)",
"fontSize": "1rem",
"lineHeight": 1
}
return (
<div style={{...styles, ...moreStyles}}>{text + (req ? '*' : '')}</div>
);
}
}
ReactDOM.render(<Label text="test" req="Yes" moreStyles={{"backgroundColor": "blue", "border": "1px solid black"}}/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Styled-component object can't observe mobx changes

I would like an HOC generated by styled-components to re-render when one of its properties get changed. I'm using MobX for change detection.
This doesn't respond to changes, I think I understand why. The question is if there is a simple workaround to make it work.
const DemoComponent = observer(styled.div`
background-color: ${props => props.myObject.myObservableIsTrue ? 'red' :
'green'};
`);
It's hard to tell by this little snippet, but one of my guesses would be you are not injecting any store, so currently, no store is being connected to your component.
here's a simple example of how I used styled-components with mobx if it helps:
EDITED:
I've updated the code example.
Do you know the Container / Presentational pattern?
This was the missing link.
In order to keep your renders as little as possible
you need to separate your stateful component from each other.
Spread them across a Container component (aka Dumb Components)
This way you separate state concerns and render only the component with the changed state.
UPDATED:
https://codesandbox.io/s/zxx6o2pq3l
Sorry!!! A bit of a hack job, but attempt to bring your entire code inside the #inject("store") class:
import React from "react";
import { observer, inject } from "mobx-react";
import styled, { css } from "styled-components";
#inject("store")
#observer
export default class OtherComponent extends React.Component {
render() {
const MyWrapper = (store) => {
const Wrapper = styled.div`
border: 1px solid black;
display: flex;
justify-content: space-between;
color: ${({ color }) => color};
border: 2px solid ${({ color }) => color || "black"};
padding: 10px;
margin-bottom: 10px;
`;
return (
<Wrapper {...store}>
styled-component
<button onClick={store.changeColor}>change color</button>
</Wrapper>
);
}
const { store } = this.props;
return (
<div>
{
MyWrapper(store)
}
</div>
);
}
}
Mobx is actually read like this: #inject("store") #observer export default class...
So it really an extension of an extended component; only wrapped variables will apply!

Testing DOM in Enzyme

Let's say I have a tiny component like this:
Button.js
import React from 'react';
import './Button.css';
export default class Button extends React.Component {
render() {
return (
<a href={ this.props.url } className={`button button-${ this.props.type }`}>
{ this.props.content }
</a>
);
}
}
And there's some super basic styling like this:
Button.css
.button {
color: white;
padding: 1em;
border-radius: 5px;
text-decoration: none;
}
.button-primary {
background-color: red;
}
.button-primary:hover {
background-color: darkred
}
.button-secondary {
background-color: aqua;
color: black;
}
.button-secondary:hover {
background-color: darkcyan;
color: white;
}
And let's say I want to write some tests for this:
Button.test.js
import React from 'react';
import Enzyme, {shallow, mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({adapter: new Adapter()});
import Button from './Button';
import './Button.css';
// Render buttons
const primaryButton = mount(
<Button
content="Primary button"
url="http://www.amazon.co.uk"
type="primary"
/>
);
const secondaryButton = mount(
<Button
content="Secondary button"
url="http://www.ebay.co.uk"
type="secondary"
/>
);
it('should exist', () => {
expect(primaryButton).toBeDefined();
expect(secondaryButton).toBeDefined();
});
it('should display text in the button', () => {
expect(primaryButton.text()).toEqual('Primary button');
});
it('should have the correct CSS classes', () => {
expect(primaryButton.find('.button').hasClass('button-primary')).toEqual(true);
expect(secondaryButton.find('.button').hasClass('button-secondary')).toEqual(true);
});
I've set this up using react-create-app and all the above works perfectly.
My question is: how do I test that what is getting rendered looks correct? For example, in this case I would want to make sure that the buttons have the correct background colours defined in the CSS file and that they have the correct border radius. This will prevent other developers accidentally overriding critical styling for example.
I was under the impression that Enzyme did this out of the box, but I cannot understand how to interrogate the virtual DOM which I assume is happening in the background? I thought that JSDOM was automatically running and I'm executing this from the CLI which is a Node environment.
I've tried this so far:
it('should have the correct background colours', () => {
const domNode = primaryButton.find('.button').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');
});
But background is returned blank, in fact if I do console.log(getComputedStyle(domNode)) I get this returned which seems to be missing the styles:
console.log src/modules/Button/Button.test.js:42
CSSStyleDeclaration {
_values: {},
_importants: {},
_length: 0,
_onChange: [Function] }
The getDOMNode of an enzyme wrapper gets you the corresponding DOM node.
You can then use getComputedStyle to get the style of that DOM:
const renderedComponent = mount(<MyComponent /);
const domNode = renderedComponent.find('div').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');

styled components: how to select child component?

// card.js
import styled from 'styled-components'
const Heading = styled.h1`
color: red;
`;
const Summary = styled.p`
text-align: center;
`;
const Card = ({className, heading, summary}) => (
<div className={className}>
<Heading>{heading}</Heading>
<Summary>{summary}</Summary>
</div>
);
export default Card;
// cardWithBlueHeading.js
import styled from 'styled-components';
import Card from './card.js';
const CardWithBlueHeading = styled(Card)`
// How can I select the Heading component and make it blue?
`;
export default CardWithBlueHeading;
I'm trying to change the styles of a child component. These styles will likely be a one off situation. I'm trying to avoid selecting the HTML element h1 and pseudo selectors.
You can pass props to styled components like below:
import styled from 'styled-components'
const Heading = styled.h1`
color: ${props => props.color};
`;
Pass the color as prop
<Heading color='blue'/>
You can give your Heading a className property and then select it with
const CardWithBlueHeading = styled(Card)`
> .[classNameYouGive] {
background: blue;
}
`;
This is the standard css way.

Categories

Resources