Rewrite styled-components in ReactJS with just 60 lines of JavaScript code
Tags: javascript, react, tutorial, webdev
Published: 2023-09-29 08:33:39

"styled-components" is a library I really like for writing CSS in React. After a few days of research into its source code, I decided to rewrite it to understand it as deeply as possible. I felt that I learned a lot from this repository, so I wrote an article to share with others what I have learned from it. To understand the code in my repository, I will explain two concepts that were relatively new to me and are essential to understand. If you are already familiar with them, you can skip ahead. ^^

## Stylesheet
If anyone is already familiar with stylesheets in JavaScript, they can skip this section. In styled-components, it is used to store styles. Each style tag corresponds to one sheet, and each sheet contains many rules. One rule corresponds to a className and its accompanying style. For example, I will illustrate adding a rule to a sheet below.
```javascript
// to get all the sheets in the app
const sheet  = document.styleSheets;
// choose 1 rule in sheet and add css to rule
sheet[1].insertRule ('.classname {width : 100%;height : 100%;}', indexStyle)
```
Detail docs about sheet: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet
## Tagged templates
The tagged templates I'm referring to here are the syntax commonly used in styled-components.
```javascript
// this function only return one ar chứ tất cả các đối số của function này
const templateFunc  =  (...args ) =>  args
templateFunc`
	width : 100%; 
    height : 50%;
	background : ${props => props};
```
The result is that we can retrieve the parameters like this.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4piv6bmd8ysok3ka234w.png)

Link to detail: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
## Let's get started writing the library
I will write a ComponentStyle class to handle the initialization of stylesheets and the insertion of CSS for components.
```javascript
class ComponentStyle {
  sheet = {};
  constructor() {
	// as soon as the class is initialized, I will create a style tag and store the sheet of that style in the 'sheet' variable within the class.
    const styleDom =  this.makeStyleTag();
  	// retrieve the sheet and save it in the this.sheet variable.
    this.sheet =  styleDom.sheet;
  }
 makeStyleTag = () => {
 	// this function will create a style and add that style tag to the head of the document.
    const style = document.createElement("style");
    style.setAttribute("data-style-duc-version", "1.0.0");
    document.head.insertBefore(style, document.head.childNodes[document.head.childNodes.length]);
    return style;
  };
  insertBefore(css) {
	// the function will generate random class names for components and return the class name.
    const className = uuid();
    const newName = "style-duc-" + className.slice(0, 5);
    this.sheet.insertRule(
      `.${newName}{${css}}`,
      this.sheet.cssRules.length
    );
    return newName;
  }
}

```
The code in the core.js file is probably the most complex part. I believe it would be much easier for everyone to understand the details of my code by cloning my [repository](https://github.com/ducga1998/rewrite-styled-components) code, running it, and then coming back here to read the explanation.

```javascript
// This function will directly create a React element.
export default function createStyledComponent(target, css) {
  let WrappedStyledComponent = {};
 // init class ComponentStyle
  const componentStyle = new ComponentStyle();
 // it saves it in the WrappedStyledComponent variable so that later, in the useStyledComponentImpl function, it can be retrieved and used.
  WrappedStyledComponent.componentStyle = componentStyle;
  WrappedStyledComponent.target = target;

  const forwardRef = (props, ref) => useStyledComponentImpl(WrappedStyledComponent, props, ref, css);
  const Element = React.forwardRef(forwardRef);
  Element.toString = () => {
    return `.${WrappedStyledComponent.newClassToString}`;
  };
  return Element;
}

const renderCss = (cssRaw, propsElement) => {
  // this function will handle the mapping of the cssRaw array into a CSS string. Each element in the cssRaw array can be either a function or a string.
  let css = "";
  for (const elementCss of cssRaw) {
    if (typeof elementCss === 'function') {
         const result = elementCss(propsElement);
          css += result;    
    }
    if (typeof elementCss === "string") { css += elementCss; }
  }
  return css;
};
// CORE FUNCTION 
// this function is responsible for creating a React component, merging props, and passing the ref.
const useStyledComponentImpl = (WrappedStyledComponent, props, ref, css) => {
  // support for the ThemeProvider in styled-components
  const theme = React.useContext(ThemeContext);
  const newProps = { ...props, ...{ theme, ref } };
  const newCss = renderCss(css, newProps);
  // insert css and get new className
  const className = WrappedStyledComponent.componentStyle.insertBefore(newCss);
  WrappedStyledComponent.newClassToString = className;
  // truncate name these className
  newProps.className = [props.className, className].join(" ");
  // finally, it creates a React element.
  return React.createElement(WrappedStyledComponent.target, newProps);
};

```
Finally, the code in the index.js file, which is the file that everyone will import whenever they use styled-components in the future.
``` javascript
// domElements is an array that contains HTML tags, e.g., domElements = ['a', 'div', 'section', 'svg', ...]. For more details, you can check my repository because it's lengthy, so I won't include it here.
import domElements from './utils/domElements';
import createStyledComponent from './core'
import css from './utils/css';
import {createContext} from 'react'
export const ThemeContext  = createContext({}) 
export const ThemeProvider = ThemeContext.Provider 
const styled  = (tag) => {
    return  (...args) => createStyledComponent(tag, css(...args));
}
domElements.forEach(domElement => {
	// I'm assigning this entity so that later everyone can use it like this: styled.div `... `
    styled[domElement] = styled(domElement);
});
export default styled


```
And there you go, you can import my fake styled-components and use it as if it were a real library. 😄

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qdnd1w8jo982ethq2fub.png)
Everyone can directly access the sandbox link to view and code with the demo.

Demo : https://codesandbox.io/embed/github/ducga1998/rewrite-styled-components/tree/master/
Repo : https://github.com/ducga1998/rewrite-styled-components

Don't forget to comment what you liked and didn't like about this tutorial and if there's something you know that I should have included in this article, do let me know in the comments down below.

Have a great one, bye! 👋

~ duncan