freeCodeCamp/docs/codebase-best-practices.md

3.7 KiB

Codebase Best Practices

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

Migrating a JavaScript File to TypeScript

Retaining Git File History

Sometimes changing the file from <filename>.js to <filename>.ts (or .tsx) causes the original file to be deleted, and a new one created, and other times the filename just changes - in terms of Git. Ideally, we want the file history to be preserved.

The best bet at achieving this is to:

  1. Rename the file
  2. Commit with the flag --no-verify to prevent Husky from complaining about the lint errors
  3. Refactor to TypeScript for migration, in a separate commit

[!NOTE] Editors like VSCode are still likely to show you the file has been deleted and a new one created. If you use the CLI to git add ., then VSCode will show the file as renamed in stage

Naming Conventions

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 = {};

Redux

Action Definitions

enum AppActionTypes = {
  actionFunction = 'actionFunction'
}

export const actionFunction = (
  arg: Arg
): ReducerPayload<AppActionTypes.actionFunction> => ({
  type: AppActionTypes.actionFunction,
  payload: arg
});

How to Reduce

// Base reducer action without payload
type ReducerBase<T> = { type: T };
// Logic for handling optional payloads
type ReducerPayload<T extends AppActionTypes> =
  T extends AppActionTypes.actionFunction
    ? ReducerBase<T> & {
        payload: AppState['property'];
      }
    : ReducerBase<T>;

// Switch reducer exported to Redux combineReducers
export 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

Within a component, import the actions and selectors needed.

// Add type definition
interface MyComponentProps {
  actionFunction: typeof actionFunction;
}
// Connect to Redux store
const mapDispatchToProps = {
  actionFunction
};
// Example React Component connected to store
const MyComponent = ({ actionFunction }: MyComponentProps): JSX.Element => {
  const handleClick = () => {
    // Dispatch function
    actionFunction();
  };
  return <button onClick={handleClick}>freeCodeCamp is awesome!</button>;
};

export default connect(null, mapDispatchToProps)(MyComponent);

Further Literature