Codebase Best Practices
Styling a component
Section titled “Styling a component”We recommend styling components using our design style guide.
The colors, fonts, and base styles are defined in the external @freecodecamp/ui component library. To contribute to this library, see our component library contribution guide. This package provides:
- Color variables and design tokens
- Font definitions and typography
- Tailwind CSS utilities and base styles
- Reusable UI components
You can import components from @freecodecamp/ui in your React code. The default client layout imports the package styles from @freecodecamp/ui/dist/base.css.
We are strongly opinionated about adding new variables/tokens to the colors. After careful research, the colors have been chosen to respect the freeCodeCamp brand identity, developer experience, and accessibility.
The !important keyword may be used to override values in some cases (e.g. accessibility concerns). You should add a comment describing the issue, so it doesn’t get removed in future refactoring.
RTL support
Section titled “RTL support”We are striving to support right-to-left (RTL) layout in the codebase for languages that are read in this direction. For this, you need to be mindful of how to style components. Here are some quick rules of thumb to follow:
- Don’t use
floatproperties- Use Flexbox and Grid layouts instead, as they have RTL support already built-in, and those will be easier to maintain and review.
- Don’t define the direction while using
marginandpadding: it may seem harmless to usepadding-rightandmargin-left, but these directions aren’t mirrored when the layout changes to RTL, and adding counter values for them in the RTL file makes maintaining the codebase harder.- Use logical properties for them: You can add the same spacing by using
padding-inline-endandmargin-inline-start, and you won’t need to worry about RTL layout, as they follow where the line starts and ends, and you won’t need to add any extra values in the RTL files, so people won’t need to remember to change the same values in two files.
- Use logical properties for them: You can add the same spacing by using
- Don’t use
!importantinfont-family: RTL layout uses different fonts compared to the LTR layout, when you add!importantin thefont-familyproperty it affects the RTL layout too.
General JavaScript
Section titled “General JavaScript”In most cases, our linter will warn of any formatting which goes against this codebase’s preferred practice.
It is encouraged to use functional components over class-based components.
Specific TypeScript
Section titled “Specific TypeScript”Migrating a JavaScript File to TypeScript
Section titled “Migrating a JavaScript File to TypeScript”Retaining Git File History
Section titled “Retaining Git File History”Git does not record renames explicitly. It detects them by comparing file contents. Commit the rename before changing the contents so that Git can identify the renamed file when displaying its history.
- Rename the file with
git mv. - Commit the rename without changing the file’s contents.
- Refactor the renamed file to valid TypeScript.
- Commit the TypeScript migration separately.
Try the rename commit normally. If the pre-commit hook fails because the renamed file is not yet valid TypeScript, create the rename-only commit with git commit --no-verify. After refactoring the file, run pnpm format and pnpm lint before committing the TypeScript migration.
Naming Conventions
Section titled “Naming Conventions”Interfaces and Types
Section titled “Interfaces and Types”For the most part, it is encouraged to use interface declarations over type declarations.
React Component Props - suffix with Props
interface MyComponentProps {}// type MyComponentProps = {};const MyComponent = (props: MyComponentProps) => {};React Stateful Components - suffix with State
interface MyComponentState {}// type MyComponentState = {};class MyComponent extends Component<MyComponentProps, MyComponentState> {}Default - object name in PascalCase
interface MyObject {}// type MyObject = {};const myObject: MyObject = {};Action Definitions
Section titled “Action Definitions”enum AppActionTypes { actionFunction = 'actionFunction'}
export const actionFunction = ( arg: AppState['property']): ReducerPayload<AppActionTypes.actionFunction> => ({ type: AppActionTypes.actionFunction, payload: arg});How to Reduce
Section titled “How to Reduce”// Base reducer action without payloadtype ReducerBase<T> = { type: T };// Logic for handling optional payloadstype ReducerPayload<T extends AppActionTypes> = T extends AppActionTypes.actionFunction ? ReducerBase<T> & { payload: AppState['property']; } : ReducerBase<T>;
// Switch reducer exported to Redux combineReducersexport const reducer = ( state: AppState = initialState, action: ReducerPayload<AppActionTypes>): AppState => { switch (action.type) { case AppActionTypes.actionFunction: return { ...state, property: action.payload }; default: return state; }};How to Dispatch
Section titled “How to Dispatch”Within a component, import the actions and selectors needed.
// Add type definitioninterface MyComponentProps { actionFunction: typeof actionFunction; value: AppState['property'];}// Connect to Redux storeconst mapDispatchToProps = { actionFunction};// Example React Component connected to storeconst MyComponent = ({ actionFunction, value}: MyComponentProps): JSX.Element => { const handleClick = () => { // Dispatch function actionFunction(value); }; return <button onClick={handleClick}>freeCodeCamp is awesome!</button>;};
export default connect(null, mapDispatchToProps)(MyComponent);Testing
Section titled “Testing”The API uses Vitest. Test files use the .test.ts extension and are stored next to the source they cover. For example, tests for api/src/utils/normalize.ts belong in api/src/utils/normalize.test.ts.
Unit Tests
Section titled “Unit Tests”Unit tests isolate a function or module. Use fixtures or mocks when the behavior under test requires them.
api/├── src/│ ├── utils.ts│ ├── utils.test.tsIntegration Tests
Section titled “Integration Tests”Integration tests exercise routes through the Fastify test server. Store each route test next to its route and use the request helpers from api/vitest.utils.ts.
api/├── src/│ ├── routes/│ │ ├── protected/│ │ │ ├── settings.ts│ │ │ ├── settings.test.ts