Functional Programming in React

Functional Programming

Let's start with some terminology

Imperative vs. Declarative Code

Declarative programming is a programming paradigm … that expresses the logic of a computation without describing its control flow.
 
Imperative programming is a programming paradigm that uses statements that change a program’s state.

Imperative vs. Declarative Code

Declarative programming is a programming paradigm in which you describe how to do something.
 
Imperative programming is a programming paradigm in which you describe what to do.

Functional Programming is a form of Declarative Programming

Not exactly, but let's not be pedantic.

Pure Functional Programming is a form of Declarative Programming

Actually, let's be pedantic ...

Referential Transparency

A given function does not access the mutable state that is external to the function.

Less precisely, a function may not have side-effects.

Referential Transparency

Given the same input, a function should always return the same output.

A function call can be replaced by its return value or another referentially transparent call with the same result.

Pure Functions

are Referentially Transparent

No observable side-effects. Easy to read and reason about.

Benefits

Explicit arguments. Expressions are self-documenting and portable.

Cacheable. Based on arguments.

Easily testable. Assert output by input. No mocking.

​Safe parallelization. No race conditions.

Pure Functional Programming in React

"In an ideal world, most of your components would be stateless functions because in the future we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations. This is the recommended pattern, when possible."

The Toolbox

Currying

f(x)(y)(z) === f(x, y)(z) === f(x, y, z)

Allows to call a function with fewer arguments. It returns a function that takes the remaining arguments until all arguments are there.

You can choose to call it all at once or simply feed in each argument piecemeal.

Currying

function add(a, b, c, d) {
  return a + b + c + d;
}
add(1, 2, 3, 4) // 10

typeof curry(add) // 'function'

typeof curry(add)(5, 2) // 'function'

curry(add)(5, 5, 5, 5) // 20

curry(add)(10)(20)(30)(40) // 100

Composing

f(a) === b
g(b) === c

g(f(a)) === c

Function breeding

Takes functions with traits you'd like to combine and mashes them together to spawn a brand new one.

First function can have any arity, all others have to be unary.

Composing

function emphasize(string) {
  return string + '!';
}

function uppercase(string) {
  return string.toUpperCase();
}

const yell = compose(
  emphasize,
  uppercase,
);
yell('hello') // 'HELLO!'

Ramda

Documentation:
http://ramdajs.com/docs/

A practical functional library for JavaScript programmers.

import R from 'ramda';

const props = { state: { counter: { count: 10 }}};
const selectCount = R.path(['state', 'counter', 'count']);
selectCount(props) // 10;

// props && props.state && props.state.counter && props.state.counter.count

const props = { state: { counter: {}}};
const selectCountOr = R.pathOr(0, ['state', 'counter', 'count']);
selectCount(props) // 0;

const props = { count: 10 };
const selectCount = R.propOr(0, 'count');
selectCount(props) // 10

Selecting props

import R from 'ramda';
import React from 'react';

const mapProps = R.curry((transformProps, Component) =>
  R.compose(Component, transformProps));

const getCurrentCount = R.pathOr(0, ['state', 'counter', 'current']);

const withCountFromState = mapProps((props) => ({
  ...props,
  count: getCurrentCount(props),
}));

const MyCounter = (props) => (<div>Count: {props.count}</div>);

export default withCountFromState(MyCounter);

Computing props

Adding static props

const mapProps = R.curry((transformProps, Component) =>
  R.compose(Component, transformProps));

const withProps = input =>
  mapProps(props => ({...props, ...input}));

const EnhancedComponent = withProps({
  myExtraProp: 'some value',
})(MyComponent);

Adding default props

const mapProps = R.curry((transformProps, Component) =>
  R.compose(Component, transformProps));

const withDefaults = input =>
  mapProps(props => ({ ...input, ...props }));

const EnhancedComponent = withDefaults({
  myExtraProp: 'some value',
})(MyComponent);

Computed or static

const mapProps = R.curry((transformProps, Component) =>
  R.compose(Component, transformProps));

const isFunction = R.is(Function);

const withProps = mapProps(props => ({
  ...props,
  ...(isFunction(input) ? input(props) : input),
}));

const EnhancedComponent = withProps((props) => ({
  myCalculacedExtraProps: doSomething(props),
}))(MyComponent);

Branching

const branch = R.curry((predicateFn, Fallback, Component) => R.ifElse(
  predicateFn,
  props => (<Component {...props} />),
  props => (<Fallback {...props} />),
));

const isVisible = R.propOr(false, 'visible');
const withFallback = branch(isVisible, RenderNothing);

const EnhancedComponent = withFallback((props) => (
  <div>This will only show if 'props.visible' is true.</div>
));

Hoisting state management

const withState = R.curry(
  (stateName, stateUpdateFn, initialState, BaseComponent) => {
    return class extends Component {
      state = { value: initialState, };

      stateUpdateFn = (fn) => {
        this.setState(({ value }) => ({ value: fn(value) }))
      }

      render() {
        const stateProps = {
          [stateName]: this.state.value,
          [stateUpdateFn]: this.stateUpdateFn,
        };
  
        return (<BaseComponent {...this.props} {...stateProps} />);
      }
    };
  });

Hoisting state management

const withCounter = withState('count', 'setCount', 0);

const Counter = withCounter(({ count, setCount }) => (
  <div>
    <button onClick={() => setCount(count - 1)}>Decrement</button>
    {count}
    <button onClick={() => setCount(count + 1)}>Increment</button>
  </div>
));

Accessing lifecycle

const withLifecycle = R.curry((methods = {}, fn) => createClass({ 
  ...methods,

  render() {
    return fn(this.props);
  },
}));

const neverUpdate = withLifecycle({
  shouldComponentUpdate(nextProps) {
    return false; // Do never update this component.
  },
});

const IdleComponent = neverUpdate(MyComponent);

Compose everything

const enhance = compose(
  mapProps(R.omit(['foo'])),
  withReducer('counter', 'dispatch', counterReducer, 10),
  withDefaultProps({ something: 'foo' }),
);

const EnhancedComponent = enhance(MyComponent);

Redux in a nutshell

const redux = (reducers, state) => {
  const listeners = [];
  const reducingFns = R.mapObjIndexed((reducer, key) =>
    R.useWith(reducer, [R.prop(key), R.identity]));
  const update =  R.converge(R.applySpec, [reducingFns]);
  const updateState = update(reducers);
  
  return {
    dispatch: (action) => {
      state = updateState(state, action)
      R.forEach(f => f(state), listeners)
    },
    subscribe: (f) => {
      listeners.push(f)
    },
  };
};

Functional Programming and React

By Sebastian Siemssen

Functional Programming and React

  • 1,629