Skip to main content
Hana allows you easily set and get your global state using the useStore() hook, and while this is super easy, it quickly gets annoying if you have to work with multiple state values, especially if they are all working towards the same feature. Reducers allow you to abstract all that into a single function which can be called from anywhere in your app, using the useReducer() hook.

Defining a reducer

At the core, a Hana reducer is just a function that accepts two arguments:
  • The current global state
  • A payload passed in from when the function is called
const setFromPayload = (state, payload) => {
  	// Perform some operations
  	return {
		...state,
		...payload,
	};
};
This function returns a new application state, while allowing you to perform additional actions.

Registering a reducer

Hana reducers are registered globally so they can be used anywhere in your app without carrying over the entire function as done in other reducer implementations. In Hana, all you need is the reducer name. You can register the reducer globally using:
  • The createStore() function right on init
  • useReducer() but this can cause problems if not done right

Using createStore()

createStore() is OPTIONALLY used to initialize your global store with default options like the initial state, reducers, modules and other items. To add reducers, you can either use a module or pass the reducer directly like this:
import { createStore } from '@hanabira/store';

createStore({
  ..., // Other config
  reducers: {
    setItems: (state, payload) => {
      // perform some operation
      const items = state.items.reverse();

      return {
        items: [
          ...items,
          ...payload
        ],
      };
    },
  },
});
The example above registers a reducer called setItems that reverses the order of the items in the state object and adds the payload to the end of the array. This reducer can be used in any component using the useReducer() hook, but we will get to that later.

**Using **useReducer()

This hook takes in the reducer function you want to register, and uses the name of the function as the name of the reducer. This means that the name of the reducer function must be the same as the name of the reducer you want to register.
We recommend using createStore() to register all your reducers instead of useReducer() because this method heavily depends on execution order. If the user calls a component that requires the reducer before it is registered, you could break your application. You should use this method to register your reducers ONLY if you can guarantee the correct execution order
import { useReducer } from '@hanabira/store';

const setItems = (state, payload) => {...};

const Component = () => {
  const addToList = useReducer(setItems);

  return (
    <div>
      <button onClick={() => addToList(['New item'])}>Add to list</button>
    </div>
  );
};
In this case, the reducer is passed directly to useReducer() . It will take the function name which is setItems() in this case, and register it globally so you can use it in other components without needing to export the function first:
import { useReducer } from '@hanabira/store';

const Component = () => {
  const setItems = useReducer('setItems');

  // items is an array of items the user wants to add to the list
  const addItemsToList = (items) => {
    setItems(items);
  };
  
  ...
};
Remember that this will only work if the component with the initial useReducer() is called first

Using a reducer

After registering your reducer globally, the easy part is using your reducer to do all the cool magical state updates with just one function. Let’s look at an example where some data is collected from an API response and synced to state:
const [, setUser] = useStore('user');
const [, setWallets] = useStore('wallets');
const [, setRecentContacts] = useStore('recentContacts');

...

const setDataFromResponse = async () => {
	const data = await getResponse();

	setUser(data.user);
	setWallets(data.wallets);
	setRecentContacts(data.recentContacts);
}
While this isn’t too bad, our setItems() from will allow us to do this same thing in a much more elegant way:
const writeToStore = useReducer('setItems');

...

const setDataFromResponse = async () => {
	const data = await getResponse();

	writeToStore({
		user: data.user,
		wallets: data.wallets,
		recentContacts: data.recentContacts,
	});
}
While this is a super simple example and will probably not happen much in production, the idea is the same. Reducers allow you to define some actions that return a new state and are usually used for complex flows.

Reducers with no values

Most implementations of reducers force you to pass a payload into your reducer function, but that’s totally optional with Hana. This allows you to define self-contained reducers that perform and internal action and update the state, for example, a logout action:
import { createStore } from '@hanabira/store';

createStore({
  reducers: {
    logout: (state) => ({
      ...state,
	  token: null,
	  refreshToken: null,
	  user: null,
	  anythingElseToUnset: null,
    }),
  },
});
And then use it like this:
import { useReducer } from '@hanabira/store';

const LogoutButton = () => {
  const logout = useReducer('logout');

  return <button onClick={logout}>Logout</button>;
};

Reducers with async actions

Hana reducers aren’t just a handy way of setting your state values, you can also use them to perform async actions like fetching values from an API:
import { createStore } from '@hanabira/store';

createStore({
  reducers: {
    addWallet: async (state, payload) => {
      const response = await linkNewWallet(payload);

      return {
        ...state,
		balance: response.data.balance,
		wallets: [
		  ...state.wallets,
		  response.data.wallet,
		],
      };
    },
  },
});
And then you can use this addWallet() reducer to add a new wallet and update the user’s balance in one elegant line:
import { useReducer } from '@hanabira/store';

const Component = () => {
  const addWallet = useReducer('addWallet');

  const addToList = async () => {
    await addWallet({...});
  };

  ...
};

Skip re-rendering UI

Sometimes, you may need to use a reducer in a component that does not actually re-render the UI after the reducer is called. In this case, you can use the useStaticReducer() hook. It works exactly like the useReducer() hook, but it does not cause the component to re-render when the reducer is called.
import { useStaticReducer } from '@hanabira/store';

const Component = () => {
  const setItems = useStaticReducer('setItems');

  const addToList = (items) => {
    setItems(items);
  };

  ...
};