AirHelp / ts-utils

Common utilities for TypeScript language.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ts-utils

Common utilities for TypeScript language.

Table of Contents

Guides

Environment-specific variables

The widest spread approach for injecting environment-specific variables to Single Page Applications which aren't served from the same domain as their web server is via .env files. It may sound as it's very similar to how it works for backend services, there is however a significant difference.

Let's say we work on Kudos.ly app written in Golang and we release a new funcionality. One of the steps in a deploy pipeline is building a docker image tagged with the git commit we're releasing. So, something like kudosly:bsdfg734bfjkdsf. Injecting ENV variables happens when we start the application:

docker run --env-file=./env kudosly:bsdfg734bfjkdsf

Now, no matter if we run this app on one of tests environments or on production we are 100% sure that it's always the same code. Only configuration is subject to change.

In case of client-side applications, the content of .env file is loaded to process.env object during compilation time (via a build tool like Webpack). So, for each environment the build process has to be repeated. Or, in other words, you deploy different code to production than you've tested.

One way to overcome it that was tested at AirHelp is to configure web server to return an appropriate .env file for a given environment always under the same path (/env):

kudos-test.ly/env -> some.storage/kudosly/env-test.json
kudos.ly/env -> some.storage/kudosly/env-production.json

However, this approach comes with a major disadvantage. Namely, your application has to wait for the /env request to finish before it can render. It's fine for internal services, but such delays in rendering customer-facing apps are bad practice.

To conclude, injecting ENV variables on compile time is good enough. However, we should invest some more time to dig into this area. If we figure out a fisible way of serving configuration and keeping build artifacts immutable without making additional HTTP calls on start up, we would also benefit from speed ups in a deploy pipeline and removing part of the load on automation servers.

Resources

Managing application state in React

React.js is not opinionated when it comes to so called global application state. There are hundreds of articles and videos online advocating for or criticising libraries which try to tackle the problem. The goal of this section is to wrap up what we have learnt about managing state in React applications so far. We start with the most basic example and gradually add some complexity to expose weak spots of implementation at given step.

Below is a simple app which presents how many kudos a user have and lets her increment this number by clicking a button as many times as she wants. Everything is contained in a single component.

const App = () => {
  const [kudos, setKudos] = React.useState(0)

  const giveKudos = () => setKudos(kudos + 1)

  return (
    <div className="app">
      <h2>Current value: {kudos}</h2>
      <button onClick={giveKudos}>Give yourself some kudos, you deserve it</button>
    </div>
  )
}

It turns out people love praising themselves, so let's give our anonymous app a name and add some features. Now, we want Kudos.ly to show kudos count in a header section and on user profile widget. The killer feature is that in both of these places, the more kudos you have the bigger the font size is going to be:

const App = () => {
  const [kudos, setKudos] = React.useState(0)

  const giveKudos = () => setKudos(kudos + 1)

  return (
    <div className="app">
      <Header kudos={kudos} />
      <UserProfile kudos={kudos} />
      <SelfKudo giveKudos={giveKudos} />
    </div>
  )
}

const Header = ({ kudos }) => (
  <div>
    <h1>Kudos.ly</h1>
    <div>
      Logged in as Anna (<KudosPresenter kudos={kudos} /> kudos)
    </div>
  </div>
)

const UserProfile = ({ kudos }) => (
  <div>
    <dl>
      <dt>Name: </dt>
      <dd>Anna Lee</dd>
      <dt>Kudos received so far:</dt>
      <dd>
        <KudosPresenter kudos={kudos} />
      </dd>
    </dl>
  </div>
)

const SelfKudo = ({ giveKudos }) => (
  <button onClick={giveKudos}>Give yourself kudos, you deserve it</button>
)

const KudosPresenter = ({ kudos }) =>
  kudos === 0 ?
    <span>zero</span> :
    <span style={{ fontSize: kudos * 1.4 }}>{kudos}</span>
)

The state implementation hasn't changed. However, we have to pass kudos and setKudos down the component tree to KudosPresenter and SelfKudo, so that they interact with a single source of truth regarding number of kudos.

As Kudos.ly flourishes and new features arrive, the component tree grows as well. It was fine to pass kudos from App via UserProfile to KudosPresenter. But what if there are 8 intermediary components? Even though they don't care about this property, they still have to take care of passing it to its child components.

Now is the moment when people start to look around for a state management library. A few years ago, Facebook open sourced their guidelines for creating UIs along with some helper libraries and called the whole architecture Flux. Based on the same patterns, Dan Abramov created Redux, which gained much more traction in the community.

Even though Redux is able to cut some complexity corners by using functional composition where Flux uses callback registration, people often complain it requires writing too much boilerplate. This is why MobX also got its share of a cake. If you're willing to get more understanding of patterns behind state management, we highly recommend those two courses from Redux creator (both are provided free of charge):

The good news is with the realease of React 16.8 it became easier to manage application state without installing (and comprehending) any additional library. What if we took the advantage of hooks and context and avoided prop drilling?

This is how the implementation would look like:

const reducer = (state, action) => {
  switch (action.type) {
    case 'GIVE_KUDOS':
      return {
        kudos: state.kudos + 1
      };
    default:
      return state;
  }
}

const StateContext = React.createContext({});

const StateProvider = ({ children, initialState }) => {
  const state = React.useReducer(reducer, initialState);

  return (
    <StateContext.Provider value={state}>{children}</StateContext.Provider>
  )
}

const useAppState = () => React.useContext(StateContext)

Now, the state implementation we had initially with useState can be removed. Instead, let's wrap our application in StateProvider:

const App = () => (
  <StateProvider initialState={{ kudos: 0 }}>
    <div className="app">
      <Header />
      <UserProfile />
      <SelfKudo />
    </div>
  </StateProvider>
)

Also, note how we got rid of passing kudos and setKudos props down the component tree. It's because we can access our application state straight from the components which need it:

const KudosPresenter = () => {
  const [{ kudos }] = useAppState()

  return kudos === 0 ?
    <span>zero</span> :
    <span style={{ fontSize: kudos * 1.4 }}>{kudos}</span>
}

const Header = () => (
  <div className="header">
    <h1>Kudos.ly</h1>
    <span className="current-user">
      Logged in as Anna (<KudosPresenter /> kudos)
    </span>
  </div>
);

const UserProfile = () => (
  <div className="user-profile">
    <dl>
      <dt>Name: </dt>
      <dd>Anna Lee</dd>
      <dt>Kudos received so far:</dt>
      <dd>
        <KudosPresenter />
      </dd>
    </dl>
  </div>
);

const SelfKudo = () => {
  const [, dispatch] = useAppState()
  const giveKudos = () => dispatch({ type: 'GIVE_KUDOS' })

  return (
    <button onClick={giveKudos}>Give yourself kudos, you deserve it</button>
  )
}

Unfortunately, such approach to managing state comes with a performance penalty. How about we let Kudos.ly users store a gallery of their photos?

const App = () => (
  <StateProvider initialState={{
    photos: [{ id: 534534, url: '/img1.jpg' }],
    kudos: 83
  }}>
    ...
  </StateProvider>
)

Now, everytime user give herself kudos and we increment this number in a state object, all components which consume StateProvider would be updated. Even the ones which display the gallery and only need to read the collection under the photos key. It's not a problem when user has one picture uploaded just like in the example above. But what if someone uploaded 126 selfies? The performance may suffer. At least, fixing it will be easy once you realize nothing stops you from having multiple state providers. Not everything has to be global.

Resources

About

Common utilities for TypeScript language.


Languages

Language:TypeScript 95.9%Language:HTML 1.8%Language:JavaScript 1.5%Language:Dockerfile 0.8%