Styled Components - why does prop position affect styling? - styled-components

I'm using styled-components to style a parent and child element in a component:
function StyledDemo({
name,
light,
...props
}) {
return (
<Parent {...props}>
<Child>{name}</Child>
</Parent>
);
}
I have a light prop which is true/false - but I'm having an issue with styling the elements based on the value of that property:
const Parent = styled.div`
background-color: #000;
width: 100%;
${props => props.light && `
background-color: #ccc;
`}
`;
The styling only seems to work when I remove the prop being passed into the function individually.
Parent element uses correct styling based on light prop value when:
function StyledDemo({ name, ...props })
Parent element does NOT use correct styling based on light prop value when:
function StyledDemo({ name, light, ...props })
I can get it all working by setting the prop on the Parent and Child component, but this doesn't seem like it's the best way:
return (
<Parent {...props} light={light}>
<Child light={light}>{name}</Child>
</Parent>
);
Is this the correct way to apply styles to components based on props, or is there an issue with my approach?
I have a demo to tinker with if it helps:
https://www.webpackbin.com/bins/-Kfcsujw99cjU7ttqgTz

This is not related to styled-components but to the rest parameter.
When you do the rest operator, any property you "pick" out by name won't be contained in the ...rest variable. So when you do
const Button = ({ light, ...rest }) => ()
<Button light primary />
rest will only contain the primary property, but not light, that's now it's own variable.
If you did
const Button = ({ ...rest }) => ()
<Button light primary />
instead rest would also contain light.
So in your example, you're picking out light from ...props, so when you pass {...props} on to the parent it doesn't contain light anymore, so styled-components doesn't know it exists! Either you go with your first version, or you have to manually apply it to each component.
See MDN for more information about the rest parameter!

Related

How to change styles of react component using styled components

I have this React component which is a simple button component:
const Button = ({ children }) => <button>{children}</button>;
I tried to pass the above component inside a styled in order to try to change its styles like this:
const StyledButton = styled(Button)`
color: yellow; //does not work
button {
color: yellowgreen; //does not work
}
`;
I am new to styled components so I am not even sure this is possible to do.
Thank you in advance for the help!
When using styled-components for your custom React components, styled-components needs to know where to inject the CSS you want to give to your <button> tag. This is done by passing the className prop to your Button component and passing it as a prop to the <button> tag.
Please try to edit your code like so:
const Button = ({ children, className }) => <button className={className}>{children}</button>;
You can read more about it here Styled Components - Existing CSS.

Why is theme undefined in styled component props?

Here I access theme by passing a callback function to the styled tag. I guess styled calls this callback function with the props as first argument. This works well.
export default function SectionHeading(props: SectionHeadingProps) {
const Heading = styled.h2`
${props => props.theme.green && `
color: green;
`}
`;
return (
<Heading>{propss.children}</Heading>
);
}
In this example I pass an expression that contains the props the component has received. Here, theme is undefined.
export default function SectionHeading(props: SectionHeadingProps) {
const Heading = styled.h2`
${props.theme.green && `
color: green;
`}
`;
return (
<Heading>{props.children}</Heading>
);
}
Why is theme undefined in the second example?
The reason is that these are different "props" and they are evaluated in different times, in the first example, the props are the props passed to the styled component, augmented with theme (provided you used <ThemeProvider .../>. In the second example, it is the props passed to your component.
The injection of the theme is done by styled-component library and only to styled components. Your component doesn't get it (because it is not a styled component).
Btw, your code has redundant nesting and creates a styled component each time it is invoked.
The way to do it is to simply define:
const SectionHeading = styled.div`
${props => (props.theme && props.theme.green && {color: 'green'})};
`;
and then:
export default SectionHeading;
Note that your sample code has a typo in the first part, you wrote {propss.children} (an extra 's').

React Virtualized windowscroller scrollElement not working

You were right that I was not passing the props in correctly. Now I have it set up as such:
Container.jsx
<div className='container' ref={(ref) => {this.foo = ref;}}>
this.renderContainer()
</div>
<Section scrollContainer={this.foo}/>
Section.jsx (just passing down props)
<Panel scrollContainer={this.props.scrollContainer}/>
Section.propTypes = { scrollContainer: PropTypes.object.isRequired }
Panel.jsx (Passing down props)
<RenderedTable scrollContainer={this.props.scrollContainer} />
RenderedTable.jsx
return (
<div className='padding-top-20 font-smoothing'>
<WindowScroller scrollElement={this.props.scrollContainer}>
{({ height, isScrolling, scrollTop, onChildScroll }) => (
<AutoSizer disableHeight>
{({ width }) => (
<Table
Unfortunately the windowScroller still does not resize. I also dont get any warnings or errors. Do you use css tricks to get the scroller to resize? I see that in the example https://bvaughn.github.io/react-virtualized/#/components/WindowScroller
you change the flex and overflow properties when changing the scrollElement from window to scrollingBody.
I know you are very busy and greatly appreciate your help!
In your example, you've assigned the scroll ref to this.tabsContainer but you're trying to access it as this.props.tabContainer. Either this is your mistake, or the example is incomplete and more context is needed. :) Can you provide a Plnkr?

How to render custom header in react-virtualized Table

In docs - headerRowRenderer, but can anyone share simple example with some custom header markup, for example with custom title attr + all 'default' virtualized features, like sortable...
Your question mentions headerRowRenderer but I think you might actually be asking about how to render a custom header cell based on the rest of your statement. Anyway, I'll show both.
// This is a custom header row rendered
// You should used all of the specified params,
// But you can also add your own decorated behavior.
const headerRowRenderer = ({
className,
columns,
style
}) => (
<div
className={className}
role='row'
style={style}
>
{columns}
</div>
)
// This is a custom header example for a single cell
// You have access to all of the named params,
// But you don't necessarily need to use them all.
const headerRenderer = ({
columnData,
dataKey,
disableSort,
label,
sortBy,
sortDirection
}) => (
<div>#</div>
)
const renderTable = (props) => (
<Table
{...props}
headerRowRenderer={headerRowRenderer}
>
<Column
dataKey='number'
headerRenderer={headerRenderer}
width={100}
/>
<Column
dataKey='name'
label='Name'
width={200}
/>
</Table>
)
Here's a Plnkr example for you: https://plnkr.co/edit/eHr3Jr?p=preview
To render a custom header, you can use React-Virtualized's Column component by passing headerRenderer prop to it.
headerRenderer is a callback responsible for rendering a cell's header column.
Here is an example showing implementation:
Method to create a custom header by returning JSX; to be declared above the render method.
You can return JSX, according to your requirements. In this example we return a paragraph (p tag).
formatCheckboxHeader = () => {
return (
<p>Custom Header</p>
)
}
In the render method, where the react-virtualized table is initialized.
<Column
width={100}
headerRenderer={this.formatCheckboxHeader}
label='#'
dataKey='id'
cellRenderer={({ rowData }) => this.formatIdColumn(rowData)}
/>
Additionally, you can pass rowData to the headerRenderer as done in the cellRenderer
Read more about headerRenderer here.

Does react-virtualized work with airbnb/enzyme?

Is it possible to use react-virtualized and enzyme together? When I try to use them together I seem to get an empty list of items in the grid.
The 2 should work together, yes. I believe the likely problem is that the react-virtualized component is being given a width or height of 0 which causes it not to render anything. (It only renders enough to fill the "window" it has.)
Assuming you're using the AutoSizer HOC- (most people do)- then one pattern I've found helpful is to export 2 versions of components- one that expects explicit width/height properties and one that wraps the other with an AutoSizer. Pseudo code would be:
import { AutoSizer, VirtualScroll } from 'react-virtualized'
// Use this component for testing purposes so you can explicitly set width/height
export function MyComponent ({
height,
width,
...otherProps
}) {
return (
<VirtualScroll
height={height}
width={width}
{...otherProps}
/>
)
}
// Use this component in your browser where auto-sizing behavior is desired
export default function MyAutoSizedComponent (props) {
return (
<AutoSizer>
({ height, width }) => (
<MyComponent
height={height}
width={width}
{...props}
/>
)
</AutoSizer>
)
}
as of react-virtualized 9.12.0 the Autosizer has defaultWidth and defaultHeight properties.
I found setting those meant enzyme tests ran correctly - rendering the child rows as expected.
<AutoSizer disableHeight defaultWidth={100}>
{({ width }) => (
....
)}
</AutoSizer>
Putting this in my test case worked for me:
import { AutoSizer } from 'react-virtualized';
// ...
it('should do something', function() {
spyOn(AutoSizer.prototype, 'render').and.callFake(function render() {
return (
<div ref={this._setRef}>
{this.props.children({ width: 200, height: 100 })}
</div>
);
});
// do something...
I use Jasmine's spyOn here, but other libraries have their own ways of overwriting functions.
Keep in mind that this is pretty fragile against future changes to the react-virtualized library (this._setRef was just yanked from the source code), and may give you false positives.

Resources