Styled-component object can't observe mobx changes - javascript

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!

Related

Problem in higher order components in react

I'm learning how to use higher order components. There I want to highlight some text. In my code I can highlight the whole line by using <div>. The problem is I only want to highlight a part of the text. So I tried <span>. But when I use span the whole highlighting part doesn't work. Since it doesn't give any error I can't understand what where the error comes from.
HighlightedText.js
import React, { Component } from 'react';
import UpdatedComponent from './Hoc';
class HighlightedText extends Component {
render() {
return <h1>Highlighted Text</h1>
}
}
export default UpdatedComponent(HighlightedText);
Hoc.js
const UpdatedComponent = OriginalComponent => {
class NewComponent extends React.Component {
render() {
return(props) =>(
<div style={{ background: 'Yellow', padding: 2 }}>
<OriginalComponent {...props}/>
</div>
)
}
}
return NewComponent;
}
export default UpdatedComponent;
Issues
Your HOC looks to be trying to return a functional component from the render method of a class-based component.
Props aren't spread correctly.
padding: 2 may not be valid, it should probably provide a unit, whatever you need
Solution
To fix the highlighting I believe you just need to specify a display: inline-block; CSS rule to the div. Spread this.props from the class-based component to the wrapped component.
const updatedComponent = (OriginalComponent) => {
class NewComponent extends React.Component {
render() {
return (
<div
style={{
background: "Yellow",
padding: "1rem", // <-- provide unit, 1rem ~ 16px
display: "inline-block" // <-- inline-block display
}}
>
<OriginalComponent {...this.props} />
</div>
);
}
}
return NewComponent;
};
If you want to highlight just a part of the text, it requires modifying the virtual DOM tree returned by the original component, since you can't just easily wrap it like in your example. You might want to use react-string-replace to achieve this.

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

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

Passing props from react-cosmos to styled-components

I have the following component where I have applied the css using styled-components:
import styled from 'styled-components'
// square-block css can be re-written as a styled-component(???)
const SquareBlock = styled.div`
width: 100%;
height: 100%;
background-color: ${props => props.color};
`
export default SquareBlock
I would like to use the following fixture with react-cosmos to adapt the background-color of the component based on the props:
import { COLORS } from '../../../constants/tetronimo'
export default {
color: COLORS.I
}
In the React developer tools I noticed that the component PropsProxy had a fixture prop which has the color property:
JSON.stringify($r.props.fixture, null, 2)
{
"color": "#3cc7d6"
}
How can I ensure that props are passed correctly to react-cosmos?
Props need to be placed under fixture.props in the latest version of React Cosmos, but you seem to have already figured this out. Does this solve your problem?

How to work with styled components in my react app?

I had trouble naming this question and it seems quite broad, so, forgive me oh moderators. I'm trying out styled components for the first time and trying to integrate it into my react app. I have the following so far:
import React from 'react';
import styled from 'styled-components';
const Heading = styled.h1`
background: red;
`;
class Heading extends React.Component {
render () {
return (
<Heading>
{this.props.title}
</Heading>
);
}
}
export default Heading;
So, just a normal class, but then I import styled components up top, define the const Heading, where I specify that a Heading really is just a styled h1. But I get an error stating that Heading is a duplicate declaration since I also say class Heading....
I'm obviously completely missing something here. All the examples online doesn't actually show how you also use this with React. I.e. where do I define my class, my constructor, set my state, etc.
Do I have to move the styled component into it's own file, i.e.:
import styled from 'styled-components';
const Heading = styled.h1`
background: red;
`;
export default Heading;
Then create a React component that will serve as a wrapper of sorts, e.g. 'HeadingWrapper':
import React from 'react';
import Heading from './Heading';
class HeadingWrapper extends React.Component {
render () {
return (
<Heading>
{this.props.title}
</Heading>
);
}
}
export default HeadingWrapper;
A bit of clarity on this would greatly be appreciated! Thanks :)
styled.h1`...` (for example) returns a React component that works just like <h1>. In other words, you use <h1> like this:
<h1>h1's children</h1>
...so when you do const Heading = styled.h1`...`;, you'll use <Heading> the same way:
<Heading>Heading's children</Heading>
If you want a component that behaves differently, e.g. one that uses the title prop instead of children, you'll need to define such a component, and it will need to have a different name than the Heading component you already defined.
For example:
const styled = window.styled.default;
const Heading = styled.h1`
background: red;
`;
const TitleHeading = ({title}) => <Heading>{title}</Heading>;
// ...or...
class StatefulTitleHeading extends React.Component {
render() {
return <Heading>{this.props.title}</Heading>;
}
}
ReactDOM.render(
<div>
<Heading>I'm Heading</Heading>
<TitleHeading title="I'm TitleHeading"/>
<StatefulTitleHeading title="I'm StatefulTitleHeading"/>
</div>,
document.getElementById('container')
);
<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>
<script src="https://unpkg.com/styled-components#1.4.3/dist/styled-components.js"></script>
<div id="container"></div>
Frankly, though, it makes more sense to just use the component returend by styled.h1 directly:
const Heading = styled.h1`...`;
export default Heading;
// ...then...
<Heading>Children go here</Heading>
The semantics of children are already clear, and using <Heading title="Children go here"/> instead detracts significantly from that.
Let me make this really simple for you.
Let's create one styled component for heading named 'Heading'
const Heading = styled.h1`
color: 'black';
font-size: 2rem;
`
and now you can use it like following.
<Heading>{this.props.title}</Heading>
How you manage to get the title prop as it's child is no concern of style component's. It only manages how that title looks. Styled component is not a container that maintains your app/business logic.
Now let's look at an example in it's entirety.
import styled from 'styled-components'
// Heading.js (Your styled component)
const Heading = styled.h1`
color: 'black';
font-size: 2rem;
`
export default Heading
and now your container that will use your styled component
// Header.jsx (Your container)
class Header extends Component {
componentWillReceiveProps(nextProps) {
// This your title that you receive as props
console.log(nextProps.title)
}
render() {
const { title } = this.props
return (
<div id="wrapper">
<Heading>{title}</Heading>
</div>
)
}
}
I hope that helps. Let me know if you need further clarification.

Categories

Resources