I am running into an off issue where my innerRef prop on a component is getting totally swallowed, and never ran.
Here is what my component implementation looks like:
<StyledPopOver
innerRef={el => (this.popOverEl = el)}
transitionState={transitionState}
animationTiming={animationTiming}
style={this.getAnchorPosition()}
{...styledProps}
>
{children}
</StyledPopOver>
And here is what the style component definition looks like:
const StyledPopOver = styled.div`
display: table-cell;
transform-origin: top;
transition-property: all;
position: fixed;
z-index: 9998;
opacity: 0;
transform: scaleY(0);
${props => {
switch (props.transitionState) {
case ENTERING:
return css`
opacity: 1;
transform: scaleY(1);
transition-duration: ${passedProps =>
passedProps.animationTiming.enter}ms;
`
case ENTERED:
return css`
opacity: 1;
transform: scaleY(1);
`
case EXITING:
return css`
transition-duration: ${passedProps =>
passedProps.animationTiming.exit}ms;
`
default:
return null
}
}};
`
If I log the props being passed into the styled component, I can see innerRef. But if I put a log in my ref setter, it never gets called.
The only thing additionally special about this is that the component is being rendered as a child of a React 16 Portal. That said, I just tested pulling the portal out of the tree, and I get the same result.
I figured this one out, and at the end of the day it is a lesson, so I figured I'd provide my solution.
It turns out that higher up in my tree, I was using "innerRef" in a way that was cascading down to children using {...styledProps}
What I learned is that innerRef should only ever be used as a prop name on a direct styled component. If not a direct styled component, it should only ever be ref (when interacting with a HTML element), or a name other than innerRef (when doing anything special about which element gets the ref handler.
Related
EDIT: Actually I was wrong, the first example also doesn't work! It also interpolates the code!!
I'm experimenting with styled-components css prop api and I ran into the following problem:
This code works well:
const myCss = css<PropsWithTheme>`
width: 100px;
height: 100px;
color: ${props => props.theme.color};
`
const MyComponent = () => <div css={myCss.toString()} />
But the following does not:
const getCss = (color: strinbg) => css<PropsWithTheme>`
width: 100px;
height: 100px;
color: ${color}; // I tried injecting the color directly
color: ${() => color}; // And also returning it from a callback inside the css``, just as I would access props/theme
`
const MyComponent = () => <div css={getCss('red').toString()} />
The output css here is width: 100px; height: 100px; color: ,red,; ,() => color,;, which is obviously not valid.
Using template string interpolation to stringify the output solves the problem, but results in very bad readability due to Prettier enforcing this format:
const MyComponent = () => (
<div
css={`
${getCss('red')}
`}
/>
)
Unfortunately moving the inerpolation anywhere outside the css prop definition ( in component body or creating a stringify function) breaks the functionality (either prop/theme access, all css doesn't get applied at all).
It seems to be related to this issue: https://github.com/styled-components/styled-components/issues/1641, but the suggested solution there is to use the css helper function, which I'm already doing :(
Is there an easy fix to my problem?
Styled Components let you use props in your CSS, eg.:
styled.whatever`
margin: ${props => props.bigMargin ? '20px' : '5px'}
`;
However, I just tried to use the props in the style name instead:
styled.whatever`
margin-${props => props.leftmargin ? 'left' : 'right'}: 10px;
`;
and when I do that, in the Chrome debugger it looks like:
margin-right: ;
: 1em;
Is it not possible to change your style names using props, or am I just doing it wrong somehow?
The only way I could get it to work was to put both the name and value into the props-controlled part:
${({ leftmargin }) =>
leftmargin ? `margin-left: 1em` : `margin-right: 1em`}};
This works, but if anyone has a better answer (a "DRY-er" one), I'll happily accept it.
I'm starting to work with styled-components and had a question about scoping.
This is just a dummy example but one that shows the point.
So I have a component. I setup a styled div called Wrapper then instead of creating another styled component to handle group, I thought be easier to just add a class to the inner div called .group and using nesting like in SCSS to style the inner div. This works but the problem with using className for the inner div is there could be a collision with global styles called .group
So, is there a way to avoid this with scoping somehow, or would I have to create another styled component called Group to handle that inner CSS ? Seems like a lot of boilerplate to have to add another styled component just to style the inner components.
const Wrapper = styled.div`
color: blue;
.group {
padding: 10px;
color: green;
}
`
const MyComponent = () => {
return (
<Wrapper>
<div className='group'>
<h1>heading text</h1>
<h2>subheading text</h2>
</div>
<div>This is my blue text</div>
</Wrapper>
);
}
Here is my globalStylesheet with group. Obviously this only has one style but it could have way more to handle grouped elements globally.
export default createGlobalStyle`
h1, h2, h3, h4, h5, h6 {
font-family: '.....';
}
.group {
background-color: red;
}
`;
I know I could also do
> div {
border: 1px solid red;
}
but I want to be able to be more explicit with a className
I think it's better to create another styled-component for group like
const Group = styled.div`
padding: 10px;
color: green;
`
So you can be sure that overwriting styles properly. And if there will be more styles in Wrapper, it stays less readable. Also you can easily replace Group component into children or make as parent(in this case you should rewrite .group style from Wrapper to another one).
In future to prevent boilerplate code you can rewrite existed styled-components like
const Timer = styled.div`
background: #ff5f36;
border-radius: 50%;
width: 48px;
height: 48px;
display: flex;
justify-content: center;
align-items: center;
font-family: GTWalsheim;
font-size: 32px;
color: #ffffff;
`
const TimeIsUp = styled(Timer)`
width: 172px;
border-radius: 8px;
`
EDIT
Also you can easily replace Group component into children or make as parent
I'll try to explain in code below
const MyComponent = () => {
return (
<Wrapper>
<div className='someClass'>
<Group> // so you can replace only your component, without rewriting any style
<h1>heading text</h1>
<h2>subheading text</h2>
</Group>
</div>
<div>This is my blue text</div>
</Wrapper>
);
}
I mean you can easily replace Group component to any place of code. While when you write style from parent as it was in Wrapper, you should replace this .group style from Wrapper to another element which is parent for .group
Im testing using react-testing-library and jest-styled-components.
I have a wrapper component that renders the styles of its child button dependant on a selected prop passed to it.
This is the code:
const selectedStyles = css`
background-image: url(../image);
background-position: center;
background-repeat: no-repeat;
border-color: ${color.grey6};
height: 38px;
width: 58px;
& span {
display: none;
}
`;
const ButtonWrapper = styled.div`
& button {
font-size: 15px;
line-height: 20px;
padding: 8px 12px;
${props =>
props.selected
? css`
${selectedStyles}
`
: ""}
&:hover,
:focus {
${props =>
props.selected
? css`
${selectedStyles}
`
: ""}
}
}
`;
and the test
test("it renders the correct styles when selected ", () => {
const { container } = render(<CheckButton selected>Add</CheckButton>);
const button = container.querySelector("button");
expect(button).toHaveStyleRule("background-position", "center");
});
but its failing with "Property 'background-position' not found in style rules" which is true for the original button, however when its parent is passed the selected prop this style applies.
I am also doing snapshot testing with the component however not testing the props getting passed brings the test coverage down.
Can anyone help?
In general as far as nested styles testing is concerned, I would recommend testing directly the nested element.
I personally haven't figured out a way to test nested styles using the .toHaveStyle(``); (not even a simple
a {
text-decoration: none;
}
)
so I ended up querying for the exact component I wanted to test, eg:
expect(screen.getByText(/text-within-the-child-component/i)).toHaveStyle(`
text-decoration: none;
`);
In your specific case I believe the way to go is to render your component in your test directly with the props that trigger the styles you want for each case (selected in your code example).
For those who are facing the same problem toHaveStyleRule accept a third "options" parameter after property and value where you can path a modifier:
test("it renders the correct styles when selected ", () => {
render(<CheckButton selected>Add</CheckButton>);
const button = container.querySelector("button");
expect(screen.getByText("Add").parentElement).toHaveStyleRule("background-position", "center", { modifier: 'button' });
});
Here I state on the fact that "Add" is the button text and its parent is the component ButtonWrapper.
By the way, you should avoid as much as possible using querySelector (here I'm using react testing library).
https://github.com/styled-components/jest-styled-components
Is it possible to use attribute selectors when using sytled-components?
&:hover, &[active='true']{
transform: translateY(-4px);
box-shadow: 0 7px 16px 0px rgba(255,63,23,0.87),
}
the idea is that then I have the following
<Button active />
Otherwise I have to duplicate the code and it becomes much more uglier.
I would use the css helper and some interpolations to avoid duplicating the code:
import styled, { css } from 'styled-components';
const hoverStyles = css`
transform: translateY(-4px);
box-shadow: 0 7px 16px 0px rgba(255,63,23,0.87);
`;
const Component = styled.div`
// If the component is hovered add the hoveredStyles
&:hover { ${hoverStyles} }
// If the active property is set add the hoverStyles
${props => props.active && hoverStyles}
`
We don't plan to implement any special behaviour with attribute selectors and props like in your code idea. We want to avoid diverging from actual CSS as much as possible to avoid confusion, highlighting/parsing issues etc.
Lots of things you might want to add special magic syntax in the CSS string for are possible with a touch of JavaScript! (see above)