Skip to content

Instantly share code, notes, and snippets.

@MQuy
Last active February 4, 2018 16:07
Show Gist options
  • Save MQuy/517620ca6079ab61869befac493cb435 to your computer and use it in GitHub Desktop.
Save MQuy/517620ca6079ab61869befac493cb435 to your computer and use it in GitHub Desktop.

Bokio Frontend State Management

State Tree

There are many ways to manage our state in frontend, but in my opinion (after debugging websites which use react), state can be classified into three categories:

  • Domain state: data that we get from our servers (list of invoices, customers, signalR ...).
  • App state: data that we use for specific components (will explain details in the section below).
  • UI state: data that represents how UI is displayed.

App State ("Dynamic")

I think best way to explain this concept is via example.

✍️ Here is the scenario:

Our website has invoice create/edit page /:company/invoices-beta/create (image below). In this scene, we have few data which need to be shared across 4 blocks like:

  • Current selected customer.
  • invoice empty data.
  • Number of invoice rows.

Create Invoice

Currently, what we do right now is that we intialize those objects in invoice scene and pass it down to every block, every nested children components and also functions to allow children components can update, therefore, this is really inefficient way(we all know) to achieve our purposes. In order to solve this problem, we will define "app state" for invoice create page and share across scene and nested children.

🚀 How to achieve this techinque
  • In Redux: we will use async reducer techinue, define reducers actions selectors and inject it when a component is mounted.

Redux

📁 Folder structure

Invoices
├── CreateOrEditInvoice
│   ├── components
│   ├── state
│   │   └── invoiceCreated.ts  <-- our state here
│   └── CreateOrEditInvoice.tsx

🐞 Code

const initialState = {
   selectedCustomer: undefined,
   invoice: undefined,
   numberOfInvoiceRows: 0,
};

const invoiceReducer = (state: State = initialState, action: Action) => {
  switch (action.type) {
    case SET_CUSTOMER:
      ...
    case UPDATE_INVOICE:
      ...
    case UPDATE_NUMBER_OF_ROWS:
      ...
  }
}

const setCustomerAction = () => {
  ...
}

const updateInvoice = () => {
  ...
}

const updateNumberOfRows = () => {
  ...
}

const mapStateToProps = ({ app }: Store) => {
  return app.invoiceCreated;
}

const mapDispatchToProps = (dispatch: Dispatch<Action>) => {
  return {
    setCustomerAction,
    updateInvoice,
    updateNumberOfRows,
  }
}

const withInvoiceCreated = asyncConnect('invoiceCreated', invoiceReducer, mapStateToProps, mapDispatchToProps);

export default withInvoiceCreated;

Checkout experiment/redux-improvement branch for more details.

  • In MobX: Will do it after finishing redux improvement 😂.

Domain state

All data that we get from our severs will be located inside this state. Our domain state has:

  • entities: get/post response will be normalized and put into this state.
  • session: current user session.
  • signalR: keep data between our server and websocket.

UI state

It is quite similar to what you are doing right now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment