Use Props in Styled Component to Change Style *Name* (Not Value) - styled-components

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.

Related

How to test style of nested component jest-styled-components

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

Problem with passing functional prop to styled components element. Is that posssible at all?

I try to pass the prop being a function (returning boolean) call to Styled Components with no effect.It looks like there is no length applied at all.
I have also tried syntax where "lenth" is fixed and only its value is function return, but stick to this version as clearer for me. also (props.length) without clear comparision and many other combinations.
That is how my styled element is styled
export const header = {
cell: styled.th`
background-color: rgba(188, 107, 63, 0.3);
vertical-align: bottom;
border: none;
padding: 0.75rem;
cursor: pointer;
vertical-align-middle;
${props => ((props.length === true) ? 'length: 30%;' : 'length: 13%;')};
`,
};
That is my function that returns desired prop
const isThisFirstOrSecondColumn = x => x === 0 || x === 1;
That is how my header.cell component is applied (part only, it's a little longer)
return (
<tr onClick={handleSort}>
{headers.map((item, index) => (
<header.cell
length={isThisFirstOrSecondColumn(index)}
key={item}
length is no valid css property and therefore your length prop has no effect. Also you would want only pass the values like this:
width: ${props => props.length ? '30%' : '13%'};
Otherwise you would have to use
${props => props.length ? css`width: 30%;` : css`width: 13%;`}
vertical-align-middle; is also not valid css. Other than that your code looks okay.

How to change static css file on API response in ReactJS

In my project i want to change the background-color and font of text. Both the properties are written in css file.
Project structure is:
|-myProject
|--public
|--src
|--package.json
All my css is written in public directory, and i have an api which give response of background-color and font. Now i want to change the properties background-color and font in css files according to api response.
Instead of trying to modify the base stylesheets, why not set these particular properties using the elements’ style attributes:
const divStyle = {
backgroundColor: /* Some color */,
fontFamily: /* Some font stack */,
};
function HelloWorldComponent() {
return <div style={ divStyle }>Hello World!</div>;
}
(adapted from the React docs)
I think the best way to do this would be to use inline style on the elements you want to change.
On api response -> set
const yourVar={
backgroundColor:##,
fontFamily:##
};
I believe that the answer from MTCoster is the best approach. depending on the structure of your app you could use the new Context API to make some sort of theme provider, so that you could pass custom styles that could be stored on your application state and that is loaded from your backend API. there are some tools that could help you integrate this feature more easily, like Styled-Components.
with Styled components you culd write something like:
import styled from 'styled-components'
import { YourComponentJSX } from '../somewhere'
// Wrap the component where you need your custom styles
const YourStyledComponent = styled(YourComponentJSX)`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
/* Color the border and text with theme.main */
// using the short-if to add a default color in case it is not connected to the ThemeProvider
color: ${props => props.theme.main ? props.theme.main : "palevioletred"};
border: 2px solid ${props => props.theme.main ? props.theme.main : "palevioletred"};
`;
// Define what props.theme will look like
const theme = {
main: "mediumseagreen"
};
render(
<div>
<ThemeProvider theme={theme}>
<App>
<YourStyledComponent>Themed</YourStyledComponent>
</App>
</ThemeProvider>
</div>
);
This way you could wrap your whole app and use custom styles saved on the app state that have been loaded from the backend and use them on really deeply nested ui components
*The code is a modification from the styled-component docs

innerRef getting completely ignored

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.

Attribute Selectors for styled-componnets

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)

Resources