freeCodeCamp/docs/codebase-best-practices.md
Shaun Hamilton 2f729976fc
feat(docs): add codebase best practices (#42591)
* feat(docs): add codebase best practices

* add anchor to linter setup

* add typescript highlighting, and basic idea

* apply silly standards

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* add stateful class component example

Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>

* encourage use of functional components

* split filename extension

Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>

* add comments for future me

* add redux structure

* add tsx to prism imports

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>
2021-07-06 20:00:09 +05:30

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