Basic scenario is such: I have a component which has width: 100% as defined in a stylesheet. Therefore it should retain the width of its parent component. I want to calculate the width of my component and apply it to my child component because I am rendering it via createPortal and I would like them to be the same width. This works in the browser. However, in my test, I am finding that window.getComputedStyle(component) is not returning any of the styles applied from the stylesheet.
As suggested, I could mock the javascript window, but that's actually counter to what I'm hoping to do, I think. I want to verify the behavior that is present in the browser, that window.getComputedStyle() returns all styles applied, not just the inline styles.
I have put a simple example into a codesandbox: https://codesandbox.io/s/goofy-wilson-6v4dp
Also here:
function App() {
return (
<div className="App">
<WidthComponent />
</div>
)
}
function WidthComponent() {
const myInput = useRef();
const [inputWidth, setInputWidth] = useState(0);
useEffect(() => {
console.log("in handleLoad");
const width = myInput.current ? myInput.current.offsetWidth : 0;
setInputWidth(width);
}, [myInput]);
return (
<div className="inherited-width" ref={myInput}>
<div style={{ width: inputWidth }} className="child-element">
Hello
</div>
</div>
);
}
// test
test("width is inherited", () => {
const { rerender } = render(
<div style={{ width: "452px" }}>
<WidthComponent />
</div>
);
const element = document.getElementsByClassName("child-element").item(0);
rerender(
<div style={{ width: "452px" }}>
<WidthComponent />
</div>
);
expect(window.getComputedStyle(element).width).toBe("452px");
});
.App {
font-family: sans-serif;
text-align: center;
width: 500px;
}
.inherited-width {
width: inherit;
}
Any help is appreciated.
However, in my test, I am finding that window.getComputedStyle(component) is not returning any of the styles applied from the stylesheet.
Note that if you're running your tests in JSDOM (i.e. every Jest test) then CSS isn't fully implemented. Specifically, the cascade part of CSS is not implemented (https://github.com/jsdom/jsdom/pull/2690). Inheritance is only partially implemented (display and visibility) (https://github.com/jsdom/jsdom/issues/2160).
I would suggest running tests that assert on computed styles only in browsers, not JSDOM. A codesandbox test is not running in an actual browser environment.
Related
I have a React component called beercard which populates an item for me and adds a like button, here's the Beercard file:
function Beercard(props) {
const [active, setActive] = useState(false);
const changeLike = () => {
setActive(!active)
}
return (
<BeerCard>
<Content>
<Lockup text={props.description} tag="h3" title={props.title}/>
</Content>
<ImagContainer>
<Like liked={active} />
<Image url={props.image}/>
</ImagContainer>
</BeerCard>
)
}
Then I have the Like component, which I will use to handle the onClick:
const Icon = styled.svg`
width: 32px;
height: 32px;
stroke: white;
stroke-width: 2px;
fill: ${props => props.liked ? 'white' : 'transparent'};
`
const like = (props) => {
return (
<LikeButton onClick={props.action}>
<Icon liked={props.liked}>
<path
id="heart-icon"
d="M16,28.261c0,0-14-7.926-14-17.046c0-9.356,13.159-10.399,14-0.454c1.011-9.938,14-8.903,14,0.454
C30,20.335,16,28.261,16,28.261z"
/>
</Icon>
</LikeButton>
)
}
It all works fine ie with the css for filled and unfilled, then if I set the state to true in the beercard file it sets the heart to active/fills it. However the onClick function is not doing anything and I'm not sure how to communicate between the two files to add this event. It's a simple on/off toggle with true and false but I don't know how to handle the event, can somebody see what I need to do?
If I understand correctly, you just need to pass along the desired function as a property to the Like component. You're actually 90% there, but you're calling props.action in onClick, but passing nothing to the action property.
Since you already have everything, you can change the line where you use your Like component to:
<Like liked={active} action={changeLike} />
I appreciate this may be a little basic here, but I'm relatively new to React and am testing the waters with various ways of applying on-the-fly styling rather than creating separate stylesheets and importing them.
I'm trying to experiment adding styles to three different elements - one via inline styles, another via a style tag, and another via a style variable - where only the inline style seems to work.
Here is my code with all 3 elements:
import React from 'react'
const App = () => {
render {
const testOneStyle = {
color: "red",
fontWeight: "bold"
};
return (
<div>
<span style={testOneStyle} className="test-one">test 1</span>
<span className="test-two">test 2</span>
<style>
.test-two {
color: red;
font-weight: bold
}
</style>
<span style={{color: "red"}} className="test-three">test 3</span>
</div>
)
}
}
export default App
Firstly, does the variable style (i.e. here) only work with class components rather than functional components?
And can someone explain why this is not rendering and how to render and apply the styles?
Thank you for any advice. Here is a StackBlitz demo: https://stackblitz.com/edit/react-tjukup
Unfortunately, <style> tags don't work in JSX the way they do in html. You are going to have to parse the the string appropriately yourself, since JSX is just javascript with syntactical sugar to convert into React.createElement() function with the right parameters. So you want to generally avoid style and head tags in JSX, but if you do, you want to use it like:
<style>
{"\
.test-two {\
color: red;\
font-weight: bold;\
}\
"}
</style>
EDIT
Also, to answer your question "does the variable style only work with class components rather than functional components?", no. The prop style is a JSX prop and works regardless of what kind of component you are using.
EDIT
And the reason why your component is not rendering, is because render() is a function that is only used in class based components. In a functional component you just directly return the JSX.
import React from "react";
const App = () => {
const testOneStyle = {
color: "red",
fontWeight: "bold"
};
return (
<div>
<span style={testOneStyle} className="test-one">
test 1 - fails
</span>
<span className="test-two">test 2 - fails</span>
<style>
{`
.test-two {
color: red;
font-weight: bold
}
`}
</style>
<span style={{ color: "red" }} className="test-three">
test 3 - works
</span>
</div>
);
};
export default App;
EDIT
As you may have observed in the snippet I have provided, you can also use strings with "`" to make it easier to enter strings in JSX
I hope this may helps you
import React from 'react'
const App = () => {
const testOneStyle = {
color: "blue",
fontWeight: "bold"
};
return (
<div>
<span style={testOneStyle} className="test-one">
test 1 - fails
</span>
<span className="test-two">
test 2 - fails
</span>
<style>
{
`.test-two {
color: green;
font-weight: bold
}`
}
</style>
<span style={{color: "red"}} className="test-three">
test 3 - works
</span>
</div>
)
}
export default App
Explaination:
You are using functional component
Class component require render method to return a JSX. Functional component can directly return JSX.
you can add style tag in your JSX but the context inside need to be string.
I use a list of styled components for displaying some info. I want this info to be sortable. The real problem I'm trying to solve is actually way more complex than what I'm demonstrating here. So any odd design choices are very specific to what I'm trying to do. I'm just mentioning it because the code I'm showing will be very simplified but it will also show some of these at first glance odd design choices.
I've read this article: https://medium.com/the-andela-way/react-drag-and-drop-7411d14894b9
Temitope Emmanuel (the author) did what I'm trying to achieve but with just a plain div. I don't know whether he tested all of what he proposes in his article.
Off to some code:
import React, { Component, Fragment } from 'react';
import styled from 'styled-components';
export default class SomeList extends Component {
constructor(props) {
super(props);
// in real problem all of these are props
// pulled off the state of a parent
this.state = {
dragging: false,
listOfChildrenInOrder: ['1', '2', '3'],
itemComponent: styled.div`
border: 1px solid black;
`,
};
}
render() {
const {
dragging,
listOfChildrenInOrder,
itemComponent: ItemComponent,
} = this.state;
const {
children,
} = this.props;
const Container = styled.div`
display: grid;
grid-template-rows: max-content;
grid-template-columns: repeat(${listOfChildrenInOrder.length}, max-content) 1fr;
`;
const Droppable = styled.div`
&:hover {
background-color: rgba(0,0,0,0.4);
}
`;
return (
<Container>
<Fragment>
{listOfChildrenInOrder.map(((cid, i) => (
<ItemComponent
draggable
key={`ic-${cid}`}
style={{
gridArea: `1 / ${i + 1} / span 1 / span 1`,
}}
onDragStart={(e) => {
this.setState({ dragging: true });
e.dataTransfer.setData('text/plain', `${cid}`);
}}
onDragEnd={() => {
this.setState({ dragging: false });
// doesn't even fire anymore
}}
>
{children.find(c => c.key === cid)}
</ItemComponent>
)))}
</Fragment>
<Fragment>
{dragging && listOfChildrenInOrder.map(((cid, i) => (
<Droppable
key={`d-${cid}`}
style={{
gridArea: `1 / ${i + 1} / span 1 / span 1`,
}}
onDragOver={(e) => {
e.stopPropagation();
e.preventDefault();
}}
onDrop={() => {
// do whatever (out of scope), doesn't get called anyway
}}
>
{children.find(c => c.key === cid)}
</Droppable>
)))}
</Fragment>
</Container>
);
}
}
I'm expecting the reconciler (Fiber) to update the DOM node without straight out replacing it in the middle of a drag operation. I'm using these things to act as highlighters. The real Problem I'm trying to solve actually makes a difference on where exactly stuff gets dropped, so the grid in the real problem is finer, with more droppables and one item component spaning multiple grid columns. Like I said: odd choices, but not without purpose.
Okay, I know now what was causing this whole operation to fail. The reason was dynamically creating new styled components in the render loop. Never do that. Just another rule of thumb learned.
This question is related to this post:
React Router v4 Match transitions using React Motion
...but I thought it deserved its own question.
I'm trying to figure out how to use the <MatchWithFade> example taken from here:
https://react-router.now.sh/animated-transitions
The problem I'm seeing is that if I have two tabs, and I want to fade between them, I'm only seeing the fade effect on one of the two tabs, and it depends on which <MatchWithFade> appears first in my code.
The relevant code is as follows:
const One = () => {
return (
<div style={{position:'absolute', top: 0, left: 0, width: 300, backgroundColor: 'orange'}}>
One one one one one one one one one one
</div>
)
}
const Two = () => {
return (
<div style={{position:'absolute', top: 0, left: 0, width: 300, backgroundColor: 'yellow'}}>
Two two two two two two two two two two
</div>
)
}
class AppBody extends Component {
render () {
return (
<div style={{position: 'relative'}}>
<MatchWithFade pattern='/one' component={One} />
<MatchWithFade pattern='/two' component={Two} />
</div>
)
}
}
In this example, navigating to /one, (using the React Router <Link> component) will cause the fade to happen, but if I navigate to /two, there is no fade. Then, if I list <MatchWithFade pattern='/two' ... /> first, then I see the fade transition to /two, but not /one.
Just using <Match> works fine, so I don't think it's a fundamental issue with how I have <BrowserRouter> configured.
I'm hoping that I'm just doing something silly, but for the life of me, I can't figure out what. Any guidance is appreciated.
UPDATE
I couldn't figure out how to made a jsbin using React Router (couldn't figure out how to reference the methods and objects on it, since I've only ever used RR via import statements). So here's the next best thing: this is a complete example that demonstrates this issue:
import React, { Component } from 'react';
import BrowserRouter from 'react-router/BrowserRouter'
import { TransitionMotion, spring } from 'react-motion'
import Match from 'react-router/Match'
import Link from 'react-router/Link';
const MatchWithFade = ({ component:Component, ...rest }) => {
const willLeave = () => ({ opacity: spring(0) })
return (
<Match {...rest} children={({ matched, ...props }) => {
return (
<TransitionMotion
willLeave={willLeave}
styles={matched ? [ {
key: props.location.pathname,
style: { opacity: 1 },
data: props
} ] : []}
>
{interpolatedStyles => {
return (
<div>
{interpolatedStyles.map(config => (
<div
key={config.key}
style={{...config.style }}
>
<Component {...config.data}/>
</div>
))}
</div>
)
}}
</TransitionMotion>
)
}}/>
)
}
const One = () => {
return (
<div style={{position:'absolute', top: 0, left: 0, width: 300, border: '1px solid black', backgroundColor: 'orange', minHeight: 200}}>
One one one one one one one one one one<br />
One one one one one one one one one one<br />
</div>
)
}
const Two = () => {
return (
<div style={{position:'absolute', top: 0, left: 0, width: 300, border: '1px solid black', backgroundColor: 'yellow', minHeight: 200}}>
Two two two two two two two two two two<br />
Two two two two two two two two two two<br />
</div>
)
}
class App extends Component {
render () {
return (
<BrowserRouter>
<div style={{padding: 12}}>
<div style={{marginBottom: 12}}>
<Link to='/one'>One</Link> || <Link to='/two'>Two</Link>
</div>
<div style={{position: 'relative'}}>
<MatchWithFade pattern='/one' component={One} />
<MatchWithFade pattern='/two' component={Two} />
</div>
</div>
</BrowserRouter>
)
}
}
export default App;
There are only very minor differences between this MatchWithFade, and the one taken from the React Router docs. The biggest difference is that I pulled out the zIndex reference, but that did not affect the behavior.
If it's relevant, I started this project using create-react-app, and I'm using React Router version 4.0.0-alpha.6.
This is an issue with the style you're applying (or not) from the MatchWithFade example. Add zIndex: 1 back to your willLeave function, as this ensures the outgoing route is over top of the incoming in order to see the opacity fade.
Then add the absolute positioning back to the wrapper div you're applying the style to (styles.fill in the website example) so that they can overlap each other.
Here is a gist with your code fixed up.
I have a simple React component that accepts a prop that hides/shows a component's children. I hide/show using the style attribute and display: none. If I use display: none !important, the component no longer re-renders when it receives new props.
Within the render method, it looks like this:
var style = {};
if(shouldHide) {
//this works
style = {
display: 'none'
};
//this does not
//style = {
// display: 'none !important'
//};
}
return (
<span style={style} {...this.props} />
);
Here's the full example: https://jsfiddle.net/ccnokes/LnrnrLy2/ (relevant lines start at line 27)
What's going on here? Am I missing something?
!important is currently unsupported - https://github.com/facebook/react/issues/1881
Does not appear they will be adding it any time soon.
They offer this as a workaround:
var sheet = document.createElement('style');
document.body.appendChild(sheet);
sheet.innerHTML = 'html {font-size: 1em !important;}';
But not sure if you want to go down that path or not.
I was able to resolve with a class switch:
//css
.hidden {display: none !important};
//jsx
var hideClass;
if(shouldHide) {
hideClass = "hidden";
}
return (
<span className={hideClass} {...this.props} />
);
Updated
I went ahead and added the workaround above.
And the fiddle here - https://jsfiddle.net/kellyjandrews/oan4grme/
Not exactly the answer you wanted, but it works :)