Skip to content
This repository has been archived by the owner on Oct 23, 2018. It is now read-only.

Front end Development

Sean edited this page Sep 22, 2018 · 9 revisions

See the readme in the root directory for explanation of setup and the scripts.

Important things to understand for our codebase are TypeScript (and ES6), React, and Redux. You should look at their websites for details as well as read some tutorials that are floating around the web.

Style

Immutability is important when it comes to states since that's how React and Redux know when to re-render. e.g.

const good = (obj, moo) => ({...obj, moo});

const bad = (obj, moo) => {
    obj.moo = moo;
    return moo
};

good does not alter obj, but bad does.

Also prefer declarative over imperative, and composition over inheritance.

React

For React components, it's good practice to split them into presentational and container components. Put simply, presentational components have props but no state, while container components manage state that they pass via props to presentational components (they also would pass handlers down via props to allow presentational components to interact with the state). Here's a good overview by Dan Abramov, a co-author of React and Redux: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

Storybook

Having separate presentational components also makes it easy to add them to our Storybook. Storybook is a useful tool for developing and documenting components in isolation. Stories live in the web/stories directory. yarn storybook to start the storybook.

Stateless Components

React.SFC<Props> is the type of a stateless functional component (hence SFC) i.e. the component is a function which just takes props (as defined by the interface Props), having no state. e.g.

interface Props {
    name: string;
    age: number;
}

const Message: React.SFC<Props> = ({name, age}) => (
    <>I'm {name}, and I'm {age} years old.</>
);

const Example: React.SFC<{name: string}> = ({name}) (
    <div>
        Welcome! <Message name={name} age={18} />
    </div>
);

p.s. <> is shorthand for React.Fragment

Jest Snapshots

Add jest snapshots for components once they're "mature", so if code changes their behaviour it's flagged when you run the test suite and you can decide whether it was intentional, in which case you should update the snapshot, or unintentional.

Redux

For components whose state we are storing in Redux, use the connect() function whose arguments mapStateToProps and mapDispatchToProps let you specify how to map the Redux state and dispatcher into props to pass to the presentational component.

Redux Thunk

Thunks are functions that can be dispatched and can dispatch actions. They are useful for async (though you can use them for other things too).

Async and Redux

You should never do async things in a reducer. Instead, when you do an async thing dispatch a started, success, and error action when appropriate, so that reducers can update the state in response to those. For example, the started action could update the state to show a loading icon, the success action could update the state with the retrieved data, and the error action could update the state to show an error.

With typescript-fsa, you can use the action creator's .async method to generate the three .started, .done, .failed actions

Example

import { actionCreatorFactory } from 'typescript-fsa';

const actionCreator = actionCreatorFactory('LIST_DISPLAY');

export const getDisplayedList = actionCreator.async<void, EntryList, string>(
    'GET',
);
// .started expects a `void` payload (i.e. nothing)
// .done expects an `EntryList` payload
// .failed expects a `string` payload

// you can `dispatch(loadList)` which will call this function,
// which itself can `dispatch` other things.
export const loadList: (
    listId: string,
) => ThunkAction<
    Promise<EntryList>,
    State,
    void,
    Action
> = listId => async dispatch => {
    dispatch(getDisplayedList.started());
    try {
        const res = await fetch(
            `${process.env.REACT_APP_API_BASE}/list/${listId}`,
            { mode: 'cors' },
        );
        if (res.status > 400) {
            throw new Error(`Server error: ${res.status} ${res.statusText}`);
        }
        const result = (await res.json()) as EntryList;
        dispatch(getDisplayedList.done({ result }));
        return result;
    } catch (e) {
        dispatch(getDisplayedList.failed(e.message));
        throw e;
    }
};
Clone this wiki locally