Skip to main content

How I structure my components

· 4 min read
Luke Owen
Lead Front End Developer @ Lunio
__snapshots__/
index.jsx
index.test.jsx
Component.mdx
styles.module.scss


// For a library there's also
component.spec.js
styles.theme1.scss
styles.theme2.scss

Breaking it down

snapshots

A folder for my snapshot tests, it contains snapshot files created by Jest.

index.jsx

The actual component code, the file is always called index.jsx because it makes the import smaller – I can import it using the directory rather than the file itself.

Components will always be functional, this example is oversimplified but anything requiring state or lifecycle events will use hooks. I don’t routinely need to write class based components anymore.

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styles from './styles.module.scss';

function Key (props) {
const {
children, className,
} = props;
const classes = classNames('u-kbd', styles.key, className);

return (
<div
className={classes}
>
{ children }
</div>
);
}

Next up is the typechecking, I use PropTypes to assign a type to every prop. Unless a prop isRequired it will also have a corresponding default prop.

Key.propTypes = {
/**
* This element can have children
*/
children: PropTypes.node.isRequired,
/**
* Additional classes
*/
className: PropTypes.string,
};

Key.defaultProps = {
className: null,
};

The final part of the file is the export. For more complex components this will also be where the wrappings are (higher order components like Redux etc).

export default Key;

index.test.jsx

This is the unit test file, I use Jest with react testing library.

At the top of every test file I have the same should be defined test, this acts as a smoke test – if it fails then it signifies bigger problems with the code.

import React from 'react';
import { render, cleanup } from '@testing-library/react';
import Key from './index';

describe('Key', () => {
afterEach(cleanup);

it('should be defined', () => {
expect(Key).toBeDefined();
});

});

Next is the snapshot test, it compares the code output to a stored version (created when the test is first ran).

it('renders correctly', () => {
const { asFragment } = render(
<Key>
key
</Key>,
);

expect(asFragment()).toMatchSnapshot();
});
});

Component.mdx

Documentation file. I use docz as a living styleguide, every variation of the component (size, colour etc) is documented here.

The mdx file is a mix of markdown and jsx, the <Playground> component is a live editable playground that renders its content in the docs.

At the end of the file is the <PropsTable> which uses the comments from the PropTypes to render a table of props.

---
name: Key
route: /typography/key
menu: Typography
---

import { Playground, PropsTable } from 'docz'
import Key from './index';

# Key

This styles a keyboard key. It can be used inline (ie it's not a block level element).

## Basic usage
<Playground>
<Key>
key
</Key>
</Playground>

## Properties
<PropsTable of={Key} />

styles.module.scss

This contains the styles that are specific to this component. More general styles are kept in regular global stylesheets.

I use CSS Modules for most styling, this means my class names are scoped locally and guaranteed to be unique. By doing this I don’t need to use BEM or SMACSS or another global CSS convention.

As a .scss file I can continue to use imports, nesting, and all the features of sass.

.key {
border: 1px solid #ccc;
font-family: monospace;
}

For libraries

If I’m writing a React library then I’ve got a few more files.

component.spec.js

I use Cypress for automated end-to-end testing, it simulates user responses and executes its tests in a browser.

I usually keep Cypress tests in a dedicated folder but for libraries I keep them with the components they test, it just makes it a little easier to keep track.

styles.theme1.scss

If my library has themes, then theme styling lives with the components. I use node-sass-glob-importer to find all the theme files and pull them together.

These files aren’t modules, the code here follows the regular CSS rules of specificity.

.theme {
@import '**/*.theme1.scss';
}