Skip to content
May 01, 2015•4 min read

UI is a function of state

React has revolutionised UI development. It might not be the final paradigm and it doesn’t mean we shouldn’t be exploring new ideas. But in any case, React is awesome and a lot of fun to use.

The core principle of React is that your entire UI is a function that takes some state (or simply data) and returns the entire rendered User Interface.

UI = fn(state)

You don’t even need React to apply this idea. Let’s explore this a bit further without bringing React in. Let’s implement such UI function and insert it’s rendered content into the page.

const Header = () => `
  <div>Logo</div>
`

const Counter = state => `
  <h1>Count: ${state.count}</h1>
`

const App = state => `
  <div id='App'>
    ${Header()}
    ${Counter(state)}
  </div>
`
document.body.innerHTML = App({ count: 5 })

That’s it. If you want to render the app with different state, call the App() function again. That’s the core concept behind React. But React does a fair bit more to help us in real world situations:

  • it helps us handle user events
  • makes it easy to nest components
  • has hooks for when components are added or removed
  • it uses a virtual dom underneath which makes this all very efficient
  • helps us reuse components very effectively

So let’s rewrite our small example app in React.

You can imagine composing entire applications from many React components, each using pieces of state and each encapsulating potentially complex and useful behaviours.

const App = ({ user, repo, org }) => (
  <Container>
    <Header user={user} />
    <RepoHeader org={org} repo={repo} />
    <Tabs>
      <Button>Code</Button>
      <Button>Issues</Button>
      <Button>PullRequests</Button>
    </Tabs>
    <Description />
    <RepoSummaryBar />
    <FileBrowser repo={repo} />
    <Readme />
  </Container>
)
ReactDOM.render(<App {...state} />, document.body)

You can see how App is still a function that takes state and outputs the entire UI.

Why is this significant? Because it’s simple and predictable. You can think of each smaller part of the application as yet another function. And all of these functions are pure and stateless. They take some arguments, return some UI. It’s easy to reason about each such function.

Changing state

What we saw so far is all great and good. But there’s a catch. In real world applications, the state changes all the time. For example:

  • server state changes, e.g. someone posts a comment
  • UI interactions, e.g. you click “Merge PR”
  • VR interactions, e.g. you turn your head
  • side effects, e.g. a game engine tick updates the world state

How do you keep the UI up to date?

This is static:

ReactDOM.render(<App state={state} />, document.body)

But this you can call again and again:

function render(state) {
  ReactDOM.render(<App state={state} />, document.body)
}

So what if we create an update function that updates the state and rerenders our UI. We can then pass this update function to our components and they can call it whenever the state needs to be updated.

let state = { count: 0 }

function update (nextState) {
  state = Object.assign({}, state, nextState)
  render()
}

const App ({ state, update }) => (
  <div onClick={() => update({ count: state.count + 1 })}>
    <div>Count: {state.count}</div>
  </div>
)

function render () {
  ReactDOM.render(<App state={state} update={update} />)
}

render()

Bam! Whenever some app component needs to update the state it can do so using the update function. For example, incrementing a counter after user clicked a button. Or storing a server response for other components to render. Calling update from anywhere in turn causes the entire application to rerender with the new state.

This single state store approach is what libraries such as redux or tiny-atom help you utilise effectively. With tiny-atom this same example would look more like:

const atom = createAtom(
  { count: 0 },
  {
    increment: ({ get, set }) => {
      const currCount = get().count
      set({ count: currCount + 1 })
    }
  }
)

const Counter = () => {
  const count = useAtom(state => state.count)
  const { increment } = useActions()
  return (
    <>
      Count: {count}
      <button onClick={increment}>Increment</button>
    </>
  )
}

const App = () => (
  <Provider atom={atom}>
    <Counter />
  </Provider>
)

ReactDOM.render(<App />, document.querySelector('root'))

This is the same principle we’ve already seen. We have a place to store all our state (atom), we have a way to update this state (increment action) and the app will rerender whenever the state changes (because Counter component is listening to store changes thanks to the useAtom hook).

In practice, in real world applications it’s more convenient and efficient to read values from the state using helper functions like this example instead of passing state and update or actions around as React props.


Thanks for reading and I hope this was interesting if you haven’t come across this idea before:

UI = fn(state)

UI development has never been so simple, thanks to React.