Functional-ish JavaScript

  • It’s easier to test
  • It’s easier to debug
  • It’s easier to reproduce issues

Be predictable, even if you can’t be pure

  • If the function needs to trigger some side effects, those effects should always be the same, given the same set of inputs. If different inputs trigger different side-effects, those effects should be very predictable given the parameters that are passed in.
  • If the function needs to consume data or state from a different scope (like closure scope, module scope, or global scope), it should be resilient to that state changing over time — or ceasing to exist entirely — based on some future state of the app.
  • A function should rarely ever mutate any of the parameters passed to it, if they’re mutable data structures like objects or arrays. Exceptions to this rule may be functions whose specific purpose is to mutate an object in a predictable way; like an arrayRemove function whose side effects are obvious.

Be conservative about state

  • Is the data you need something that can be derived, based on some inputs or some existing state you already have? If so, it’s best not to explicitly store it as state.
  • Is the data something which is used once and thrown away? If so, don’t persist it in any state or scope that will outlive its usefulness.
  • Is it a piece of data you will need later, or something that is expected to change over time over the lifecycle of your app? Now you might have a good reason to store it in state.

Use scoping to your advantage

  • If it’s only needed for the current piece of logic, store it in the current block scope using const or let.
  • If it’s needed for the entire duration of the function call, store it in a let or const variable that’s declared at the top level of the function.
  • If it’s something which needs to exist for a longer time — say, until an asynchronous call completes— store it in closure scope.
  • If you’re using React, keep state as close to the component(s) where you need it as possible, and don’t hoist it too high up the component chain.
  • If it needs to keep track of something across multiple function calls, persist it in the scope of a parent function, or potentially store it in the module scope.
  • If the state needs to persist across multiple pages, store it in a temporary server store — like a session — or in localStorage.
  • If it needs to exist for an extended period of time, store it in a database or a filesystem.

Encapsulate state

  • You could manually create a state variable to save the result, and only re-call the function if that variable is not already populated.
  • Or, you could abstract away the state in a reusable memoize function:

Don’t worry too much about exceptions

Use promises, or async/await, over callbacks

Use classes, if they fit your purpose

  • How well the state is encapsulated.
  • How long the state is kept around for.
  • How many side-effects the code has.

Use types

Act immutably, when possible

Write functional-ish tests

  • Is it designed for a developer to integrate? Then your tests should act as if they are that developer.
  • Is it designed for an end-user? Then your tests should act as if they’re that end user (as closely as possible).

Be pragmatic

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store