Skip to content

harryhope/hooked-on-redux

Repository files navigation

hooked-2x


Tests

Hooked on Redux is a React hook that lets you wield the power of Redux with an interface that's as simple as the useState hook.

Redux is a great way to manage React state and its core principle: a single, immutable application state, allows for great features like hot reloading and time travel debugging. Unfortunately, Redux is also intimidating to learn for beginners and requires a lot of boilerplate that can slow down application development and add overhead to your codebase.

Hooked on Redux attempts to address these shortcomings by managing actions, selectors and reducers behind the scenes and exposing a simplified hook that lets you update Redux state in the same way you would update local component state with the useState hook.

It looks like this:

const Example = props => {
  const [count, setCount] = useHookedOnState('app.components.counterValue', 0)
  return (
    <main>
      <p>You clicked {count} times.</p>
      <button onClick={() => setCount(count + 1)}>Click Me</button>
    </main>
  )
}

Getting Started

Installation

Hooked on Redux requires react-redux as a peer dependency. You'll also need react, react-dom, and redux if you don't have them installed already. This guide also assumes you are using npm and a module bundler like webpack. To get started:

npm i hooked-on-redux react-redux redux react-dom react

Importing Dependencies

Hooked on Redux works with Redux and React, so we need to install all of the typical dependencies as well as hooked-on-redux.

Hooked on Redux can also work with your existing React/Redux app, so if you already have React and Redux installed you can skip importing from react, react-dom, redux and react-redux.

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import {createHookedOnReducer, useHookedOnState} from 'hooked-on-redux'

The Store

Hooked on Redux uses a Redux store (either a new or existing one) along with a reducer creator called createHookedOnReducer. This reducer creator will automatically manage state transformations so you won't need to think about reducers, actions or action creators.

const reducer = createHookedOnReducer()
const store = createStore(reducer, {})

Provider

Hooked on Redux leverages the <Provider /> component from react-redux. If you've ever used Redux with React before you are probably already familiar with this step. In fact, most of this code should look identical to the react-redux quick start guide.

ReactDOM.render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('app')
)

The Hook

You are now ready to start writing components using useHookedOnState.

useHookedOnState is a react hook that looks and functions similar to the useState hook. The only difference is it allows you to update a Redux store instead of local component state by specifying a slice of the global state to modify along with a default value in case nothing is there.

const Counter = props => {
  const [count, setCount] = useHookedOnState('app.components.counterValue', 0)
  return (
    <main>
      <p>You clicked {count} times.</p>
      <button onClick={() => setCount(count + 1)}>Click Me</button>
    </main>
  )
}

Now, whenever you use the setCount function you defined, it will immutably update the Redux store to look like:

{
  app: {
    components: {
      counterValue: 1
    }
  }
}

You can update any slice of the global state this way by providing a path as the first parameter of useHookedOnState. The path string parameter works identically to lodash's _.set.

API

createHookedOnReducer

createHookedOnReducer(initialState, namespace, handlers)

Creates a Redux reducer meant to be used with createStore.

Arguments

initialState: (any) The initial state of the slice of the Redux store controlled by the hooked-on-reducer. Use this parameter to define pre-existing state. Default value: {}

namespace: (string) Hooked on Redux generates Redux actions behind the scenes without you having to do anything. To allow interoperability with other actions, Hooked on Redux prefixes its own actions with a string. Anything with this string will be funneled through the hooked-on-reducer this function creates. Default Value: 'HOOKED_ON_REDUX'

handlers: (object) If you need to add your own reducers to handle complicated state transforms or leverage existing reducers in your application, you can pass them through to handlers. For more information, see Complex Actions in redux-updeep (which this library is built upon).

export default createHookedOnReducer(initialState, 'MY_NAMESPACE', {
  'MY_NAMESPACE/COMPLEX_ACTION': (state, action) => {
    return complexTransformation(state, action.payload)
  }
})

Returns: (function) This function returns a function that can be used in Redux's createStore.


useHookedOnState

useHookedOnState(selector, defaultState, options)

Arguments

selector: (string) Takes a path string similar to what you would use in lodash's _.set. This path specifies the "slice" of the store that you will be modifying.

defaultState: (any) This is the default value that will be used if the "slice" of the store specified by selector is empty. It works very similarly to useState's default state.

options: (object) A configuration object that may contain the following properties:

  • namespace: (string) If you are using a custom namespace for createHookedOnReducer then you must specify that namespace as the third parameter. If you are not using a default namespace then you can ignore this. Default Value: 'HOOKED_ON_REDUX'

  • rootPath: (string) If the Hooked on Redux reducer is not at the root level of your store, you must specify the subpath it exists on with this parameter. This usually happens if you integrate Hooked on Redux into a larger Redux codebase using something like combineReducers. For instance, if the Hooked on Redux reducer is added to the combined reducers with the name myReducer, then rootPath should be myReducer as well. For a nested path you may specify a path such as path.to.my.store.

Returns: (array) [value, updateValue] This function returns a "tuple" much like useState. The first array element value is the value at the slice of state. The second element of the array updateValue is a function that accepts a single parameter that updates the global state at the slice of state specified by selector.

Prior Art

Hooked on Redux is inspired by (and used to leverage) updeep and redux-updeep. In particular, this article: How we reduced boilerplate and handled asynchronous actions with redux.