JavaScript Template Literals and styled-components
Template Literals
Template literals in JavaScript are basically regular strings on roids. The syntax for writing them is with backticks `` instead of regular string quotes. You can place expressions into those template literals which will be evaluated and replaced with the resulting values of those expression.
const name = 'Celsius';
`My name is ${name}. What's yours?` // My name is Celsius. What's yours?
Here is how the evaluation process of a template literal works:
The template literal is broken down into an array of strings and a list of variables. In the example above the array of strings would be [ 'My name is ', '. What\'s yours?' ]
whereas the list of variables would consist only of the evaluated value of the name
variable, which is "Celsius".
Then, this array of strings and the list of evaluated values is passed to a function which can concatenate all those strings and return the phrase "My name is Celsius. What's yours?" That is in fact what the default function for template literals does. It simply concatenates the strings in the array, inserting the values of the evaluated expressions between them. A sample implementation of this default template literal function could look like this:
function defaultTemplateLiteralFunction(strings, ...values){
return strings.reduce((acc, v, i) => {
acc += v;
acc += values[i] || '';
return acc;
}, '');
}
What this function does is simply concatenate the values from the strings array and the list of evaluated values alternatingly, starting with the list of strings.
Tagged Templates
JavaScript let's you define your own function to process the arguments extracted from the template literal. If you write a function right before the template literal this function will be used to process the arguments extracted from the template literal by JavaScript. This is called a tagged template.
function tag(strings, ...values) {
return `Hey ${values[0]}, nice to meet you. I am a tagged template :)`;
}
tag`My name is ${name}. What's yours?`; // Hey Celsius, nice to meet you. I am a tagged template :)
The above syntax can be thought of as invoking the tag function passing it the template literal (already processed into an array and values) as arguments.
Note, that tagged templates do not have to return strings! You can return any kind of value you like from your tagged templates. Yes, even a React component, which is really just a function anyway.
Using Tagged Templates
You can use tagged templates anywhere you want to have more control over template literals and return different values based on the contents of the template literal.
One very popular library that uses template literals is the styled-components library for React and React Native applications. To create a simple styled h1
component you could write something like the following:
const fontSize = calculateFontSize();
RedCenteredText = styled.h1`
color: red;
text-align: 'center;
font-size: ${fontSize};
`
In this case styled.h1
is a tagged template function, receiving the contents of the template literal. The template function parses the received CSS, replaces the placeholder variables with the appropriate values and returns an H1
React component with the given styles.
RedCenteredText
is a React component which you could then use in your markup just like any other React component such as:
<div>
<RedCenteredText/>
</div>
So far, we've only ever passed simple variables to be evaluated by the template literal. However, functions can also be passed as values to the template literal. If you have worked with the styled-components library before you will probably be familiar with how the following code snippet works:
RedOrWhiteCenteredText = styled.h1`
color: ${props => props.error ? 'red', 'white'};
text-align: 'center;
font-size: ${fontSize};
`
What happens here is that we are not only passing a variable but also a function to the template literal. The function processing the template literal will now return an H1 React component with either red or white text color depending on whether or not the error
prop passed to that component is true.
Styled HTML elements
Let's try to work our way towards writing a tag function that takes a template literal with parametrized CSS and returns a React component whose styles adapt to the props passed to it.
Here is how you could write a sample styled.div
tag function that takes in a string of CSS styles and returns <div>
element with those given styles as inline styles.
const styled = {
div: (strings, ...values) => {
const styles = strings[0]
.trim()
.replace(/\n/gi, "")
.replace(/\s\s/gi, "");
return `<div style="${styles}"></div>`;
}
}
const StyledDiv = styled.div`
background-color: red;
width: 50px;
height: 50px;
text-align: center;
`;
console.log(StyledDiv)
//<div style="background-color: red;width: 50px;height: 50px;text-align: center;"></div>
We remove all unnecessary whitespaces by trimming, replacing new lines and replacing double spaces with single spaces and then simply pass the resulting value as the contents of the style
attribute to a <div>
.
Now, what if we want to pass variables to the template literal? We make use of our defaultTemplateLiteralFunction
defined above to preparse the parametrized styles into a string of temporary styles, which then undergoes the same treatment as the styles from our previous example:
function defaultTemplateLiteralFunction(strings, ...values){
return strings.reduce((acc, v, i) => {
acc += v;
acc += values[i] || '';
return acc;
}, '');
}
const styled = {
div: (strings, ...values) => {
const tempStyles = defaultTemplateLiteralFunction(strings, ...values);
const styles = tempStyles
.trim()
.replace(/\n/gi, "")
.replace(/\s\s/gi, "");;
return `<div style="${styles}"></div>`;
}
}
const red = "green";
const size = "75px";
const StyledDiv = styled.div`
background-color: ${red};
width: ${size};
height: ${size};
text-align: center;
`;
console.log(StyledDiv)
// <div style="background-color: green;width: 75px;height: 75px;text-align: center;"></div>
Creating HTML elements with inline styles from tagged template literals is fun and all but doesn't really seem all that useful, as there is no way to use the generated HTML without some extra effort.
Styled React components
Now let's figure out how to return a fully-fledged React component from a template literal function.
import React, { Component } from "react";
/**
* @function parseStyles
* Parses a string of inline styles into a javascript object with casing for react
* Source: https://gist.github.com/goldhand/70de06a3bdbdb51565878ad1ee37e92b
*
* @param {string} styles
* @returns {Object}
*/
const parseStyles = styles =>
styles
.split(";")
.filter(style => style.split(":")[0] && style.split(":")[1])
.map(style => [
style
.split(":")[0]
.trim()
.replace(/-./g, c => c.substr(1).toUpperCase()),
style.split(":")[1].trim()
])
.reduce(
(styleObj, style) => ({
...styleObj,
[style[0]]: style[1]
}),
{}
);
const styled = {
div: (strings, ...values) => {
return function(props) {
function defaultTemplateLiteralFunction(strings, ...values) {
return strings.reduce((acc, v, i) => {
acc += v;
if (!values[i]) {
acc += "";
return acc;
}
acc += typeof values[i] === "function" ? values[i](props) : values[i];
return acc;
}, "");
}
const tempStyles = defaultTemplateLiteralFunction(strings, ...values);
const styles = tempStyles.trim().replace(/\n/gi, "");
if (!styles) {
return null;
}
let parsedStyles = parseStyles(styles);
return React.createElement(
"div",
{
...props,
style: parsedStyles
},
"Hello"
);
};
}
};
const red = "red";
const size = "100px";
const StyledDiv = styled.div`
background-color: ${red};
width: ${props => (props.small ? size : size * 2)};
height: ${props => (props.small ? size : size * 2)};
text-align: center;
`;
class App extends Component {
render() {
return (
<div className="App">
<StyledDiv small />
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
// If you take away the `small` prop it doesn't work since the multiplication of 100px * 2 returns NaN and I didn't feel like implementing px value times integer multiplication for this proof of concept.
So what's happening here? The parseStyles
function takes a string of inline styles and parses them into a JS object with the correct casing (camelCase) so that these styles can be used within a React component. The code for this was taken from here.
Our implementation for a div
styled component now returns a React component, which is simply a function that returns a React element. Inside this function, we define defaultTemplateLiteralFunction
with a slightly different implementation than above. Defining the function inside the functional component is done, so that the function captures the props which are passed to the React functional component.
When we now call
styled.div`
style1:...
style1:...
etc.`
the passed styles are first processed just as before by concatenating the them into a single string, replacing the parametrized values. The crucial difference in this implementation of the defaultTemplateLiteralFunction
is that if we detect a parametrized value to be a function, we call that function, passing it the props of the functional component.
At this point all parametrized values have been replaced and we have a string of styles in tempStyles
. All that's left to is turn this string into a JS object so that it can be used as a React style object. The parseStyles
function does just that.
Now that we have a proper JS style object we can create our React Element, passing it the parsed styles and returning it from our functional component.
Et voilá, we have a poor man's implementation of div styled-components.