Thursday, June 9, 2022

Jotai State + React Hooks === React Native easy state system

 We moved our local state management recently to Jotai.   It has greatly simplified state mangement for our React Native app.   Now the hooks act as the complete state manager, so that the UI portion doesn't need to worry about it.    

I won't give detailed info about Jotai here, as you can look at other tutorials/docs for that.   But serveral key points:

  • App wide state is very easy and straightforward with jotai atoms
  • Backing the jotai atom with AsyncStorage means state can automatically be saved/rehydrated between app runs
  • Business logic can be easily automated in the hook, so it is reused throughout the app
  • using derived jotai atoms means your calculated states are always in sync with other state changes


Here's an example from a useCustomer hook.   Name and phone are easily stored and will stay even if I shut the app down and bring it back up,  without writing any code on app startup!

import AsyncStorage from '@react-native-community/async-storage';
import {atom, useAtom} from 'jotai';
import {atomWithStorage} from 'jotai/utils';

import {
formatPhoneDisplayWithParens,
formatOnlyNumber,
NAME_REGEX,
} from '../utils/text';

const ATOM_STORAGE = {
...AsyncStorage,
delayInit: true,
};

const firstNameAtom = atomWithStorage('firstName', '', ATOM_STORAGE);
const lastNameAtom = atomWithStorage('lastName', '', ATOM_STORAGE);
const phoneAtom = atomWithStorage('phone', '', ATOM_STORAGE);

// Automatically creates a display version of the phone number every
// time the phone atom is changed, but ONLY when it changes
const phoneDisplayAtom = atom(get =>
formatPhoneDisplayWithParens(get(phoneAtom)),
);



function useCustomer() {
const [firstName, setFirstNameValue] = useAtom(firstNameAtom);
const [lastName, setLastNameValue] = useAtom(lastNameAtom);
const [phone, setPhoneValue] = useAtom(phoneAtom);

const [phoneDisplay] = useAtom(phoneDisplayAtom);

// Automatically handles limiting name to certain characters
const setFirstName = value => {
const newValue = value.match(NAME_REGEX) ? value : firstName;
setFirstNameValue(newValue);
};

// Automatically handles limiting name to certain characters
const setLastName = value => {
const newValue = value.match(NAME_REGEX) ? value : lastName;
setLastNameValue(newValue);
};

const setPhone = value => {
const newValue = formatOnlyNumber(value);
if (newValue?.length > 10) {
return;
}
setPhoneValue(newValue);
};

const hasCustomerData = () => {
let valid = false;

firstName && (valid = true);
lastName && (valid = true);
phone && (valid = true);

return valid;
};

return {
firstName,
lastName,
phone,
phoneDisplay,
setFirstName,
setLastName,
setPhone,
hasCustomerData,
};
}

export default useCustomer;