Create Styled Components
Let’s learn how to use CSS in JS to make styled-components.
We'll cover the following
Creating a styled component
Currently, the <img>
created by CarouselSlide
is unstyled, which means that it scales to whatever the original size of the image is. That means that the carousel jarringly expands and contracts as users move from slide to slide. Worse, it’ll push other page content around in the process.
-
To do that, we’ll replace the unstyled
<img>
element with a component generated by styled-components:// src/CarouselSlide.js import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; const Img = styled.img` object-fit: cover; width: 100%; height: 500px; `; const CarouselSlide = ({ imgUrl, description, attribution, ...rest }) => ( <figure {...rest}> <Img src={imgUrl} /> <figcaption> <strong>{description}</strong> {attribution} </figcaption> </figure> ); ...
styled.img
is a function that generates a component that renders an <img>
tag with the given styles. When an instance of that Img
component is mounted, styled-components will dynamically insert a style rule with the styles you provided, using a class name selector based on the hash of those styles.
There’s some syntactic fanciness here in the form of an ES6 feature called tagged templates: If you put a function directly in front of a template string (the kind delimited by backticks), that function is called with the template string as an argument.
In the case of Img
, you could use the normal function call syntax since the string with the styles is a constant. The tagged template syntax unlocks new possibilities when the string has interpolations (the ${...}
syntax). Each piece of the interpolated string is passed into the function as a separate argument. That gives the tag function the chance to process interpolated variables. As we’ll soon see, styled-components take advantage of this power.
As soon as you hit save, you should see the difference in your browser. Before, the size of the <img>
tag was determined by the image file it loaded. Now, it takes up the full width of its container and has 500px of height. The object-fit: cover
rule means that the image keeps its aspect ratio as it expands or contracts to those dimensions, getting clipped as needed.
Why 500px? Really, the height of the image should be determined by the app rendering the carousel component.
-
So let’s make these styles dynamic:
// src/CarouselSlide.js import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; const Img = styled.img` object-fit: cover; width: 100%; height: ${props => // 1 typeof props.imgHeight === 'number' ? `${props.imgHeight}px` : props.imgHeight}; `; const CarouselSlide = ({ imgUrl, imgHeight, description, attribution, ...rest }) => ( <figure {...rest}> <Img src={imgUrl} imgHeight={imgHeight} /> <figcaption> <strong>{description}</strong> {attribution} </figcaption> </figure> ); CarouselSlide.propTypes = { imgHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // 2 imgUrl: PropTypes.string.isRequired, description: PropTypes.node.isRequired, attribution: PropTypes.node, }; CarouselSlide.defaultProps = { // 3 imgHeight: 500, }; export default CarouselSlide;
- This is where styled-components really get exciting. Interpolated values in the style template can be a function of the component’s props. Whereas ordinary CSS is static, these styles are completely dynamic. If the
imgHeight
prop changes, the styles update automatically.- This code declares
imgHeight
as a prop that can be either a number (indicating a px value) or a string (such as'100vh'
). Since it doesn’t haveisRequired
, it can also benull
. In that case, styled-components would simply omit theheight
rule from the generated styles.- A React component’s
defaultProps
are used as fallbacks when the prop’s given value isundefined
. WhereaspropTypes
are only used during development,defaultProps
are always applied.
Note that the src
prop passed to Img
is passed through to the <img>
element it renders. Styled-components filter out props like imgHeight
that aren’t valid DOM attributes. This means you should be careful what prop names you use for styling. If, for example, we’d named the prop height
instead of imgHeight
, then it would’ve been passed down as the height
DOM attribute of the <img>
.
Right now, imgHeight
can be overridden on a slide-by-slide basis since Carousel
passes the whole slide data object down to CarouselSlide
as props. But in most cases, the Carousel
consumer will want it to have a consistent height.
-
So let’s add a prop to
Carousel
that overrides the defaultimgHeight
onCarouselSlide
:// src/Carousel.js ... export default class Carousel extends React.PureComponent { static propTypes = { defaultImgHeight: CarouselSlide.propTypes.imgHeight, slides: PropTypes.arrayOf(PropTypes.shape(CarouselSlide.propTypes)) .isRequired, }; static defaultProps = { defaultImgHeight: CarouselSlide.defaultProps.imgHeight, // 1 }; ... render() { const { defaultImgHeight, slides, ...rest } = this.props; return ( <div {...rest}> <CarouselSlide imgHeight={defaultImgHeight} // 2 {...slides[this.state.slideIndex]} /> <CarouselButton data-action="prev" onClick={this.handlePrevClick}> Prev </CarouselButton> <CarouselButton data-action="next" onClick={this.handleNextClick}> Next </CarouselButton> </div> ); } }
- The default value from
CarouselSlide
’simgHeight
is reused as the default value fordefaultImgHeight
. Functionally, this is redundant, butdefaultProps
is also commonly used for documentation, as we’ll see in the chapter “Continuous Integration and Collaboration”.- Importantly,
defaultImgHeight
is passed down asimgHeight
before the slide data object spreads, giving the latter precedence. If it were the other way around, individual slides would be unable to overrideimgHeight
.
If you feel naked without test coverage for all of these changes, you can skip ahead to Testing Components, then come back for a detour into styled-components tooling.
So, How did styled-components get our styles into the <img>
tag? If you inspect one of the <img>
tags in the browser, as in the next screenshot, you’ll see that its class
attribute is full of gibberish. Something like class="sc-bdVaJa hhfYDU"
. The styled-components library generated these class names for you and injected a corresponding style rule into a <style>
tag in the <head>
of the page.
Get hands-on with 1400+ tech skills courses.