🧸
zfy
  • Getting started
  • 🤓Guides
    • Creating & using a store
    • Persisting & rehydrating data
    • Using middlewares
    • Handling multiple stores
    • Type checking with TypeScript
  • 📚API reference
    • Types
      • CreateStoreType
      • CreateStoreConfigType
      • CreateStoreOptionsType
      • StoreType
      • ZfyMiddlewareType
      • InitStoresType
      • InitStoresResetOptionsType
    • createStore
    • initStores
    • PersistGate
    • useRehydrate
  • 🗃️Others
    • Help
    • Contributing
    • Changelog
    • GitHub
Powered by GitBook
On this page
  • Creation
  • Usage
  • How to access data
  • How to update data

Was this helpful?

  1. Guides

Creating & using a store

PreviousGetting startedNextPersisting & rehydrating data

Last updated 3 years ago

Was this helpful?

zfy general philosophy is to always behave as you'd expect zustand to, unless when we can provide an even better experience. Creating a store is an example of such exceptions.

If you're looking for how to bundle and manage several stores at once, look into the guide.

Creation

is the function that allows you to create a zustand store. As your primary interface with the library, we tried to make it as simple to use as possible. It only expects the store name, its default data and you can also provide some options. That last argument is where you can enable the middlewares zfy provides out-of-the-box, like or or provide your own via .

src/stores/user-store.ts
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createStore } from '@colorfy-software/zfy'

import type { StoresDataType } from '../types'

export const initialState: StoresDataType['user'] = {
  id: '',
  likes: 0,
}

export default createStore<StoresDataType['user']>('user', initialState, {
  log: true,
  persist: { getStorage: () => AsyncStorage },
})
src/types.ts
export interface StoresDataType {
  user: {
    id: string,
    likes: number,
  }
}

Usage

We decided to follow a specific set of rules that dictate how we use zustand stores. These decisions trickled down into some aspects of zfy's API that apply here.

It's important to note that, of course, zustand still works as you'd expect, (meaning all the items imported from 'zustand'). zfy only aims at enhancing not replacing them.

Disregarding the points covered below will prevent zfy from showcasing its full potential (and will potentially lead to undesired behaviours).

How to access data

No matter which type of data you want to put inside a store: it will always be available from getState().data, not the top level getState().

This might sound quite restrictive or overdoing it at first but such an approach helped us simplify and harmonize how stores are created and used throughout the codebase. This is also what powers some of zfy's core features.

That's why:

Any store created with zfy always exposes the same 4 elements from the getState() object: name,data,update()&reset(). And we expect you to only need & use those 4 elements.

Therefore, accessing your data will always look the same. Eg:

import shallow from 'zustand/shallow'

import userStore from './stores/user-store'
import settingsStore from './stores/settings-store'

const Component = (): JSX.Element => {
  const appLanguage = settingsStore(data => data.language)
  const [firstName, lastName] = userStore(
    (data) => [data.firstName, data.lastName],
    shallow
  )
  
  // ...
}
import settingsStore from './stores/settings-store'

const appLanguage = settingsStore.getState().data.appLanguage

How to update data

update()

src/screens/Login.tsx
import core from '../core'
import navigation from '../navigation'
import appInfoStore from '../stores/app-info-store'

const updateAppInfo = appInfoStore.getState().update

const Login = (): JSX.Element => {
 const onPressLogin = async (email, password) => {
  try {
    // ...
    await core.user.login(email, password)

    // 👇
    updateAppInfo(data => {
      data.lastLoginAt = Date.now()
    })

    await core.messages.fetchInbox()

    navigation.to('Home')
  } catch (error) {
    // handle error
  } finally {
    // ...
  }
 }

 // ...
}

export default Login
src/core.js
import Api from '../api'
import userStore from '../stores/user-store'
import messagesStore from '../stores/messages-store'

const updateUser = userStore.getState().update
const updateMessages = messagesStore.getState().update

export default {
  user: { 
    login: async (email, password) => new Promise((resolve, reject) => {
      try { 
        const userData = await Auth.login(email, password)
        
        // 👇
        updateUser(data => {
          data = userData
        })

        resolve(userData)
      } catch (error) {
        reject(error)
      }
    })
  },

  messages: {
    fetchInbox: async () => new Promise((resolve, reject) => {
      try {
        if (!(await Auth.isLoggedIn())) {
          return resolve(messagesStore.getState().data.inbox)
        }

        const inboxMessages = await Api.fetchInbox()

        // 👇
        updateMessages(data => {
          data.inbox = inboxMessages
        })

        resolve(inboxMessages)
      } catch (error) {
        reject(error)
      }
    }),

    markAsRead: async (messageId) => new Promise((resolve, reject) => {
      try { 
        if (!(await Auth.isLoggedIn())) return resolve(false)

        await Api.markMessageAsRead(messageId)
        
        // 👇
        updateMessages(data => {
          const index = data.inbox.findIndex(item => item.id === messageId)
        
          if (index !== -1) {
            messages.data.read.unshift({
              ...messages.data.inbox[index],
              readAt: date.now(),
            })
            messages.data.inbox.splice(index, 1)
          }
        })

        resolve(true)
      } catch (error) {
        reject(error)
      }
    })
  }
}

reset()

This is your go-to method when you simply want to reset your store to its initial default data. It can be useful for when your users are logging out for instance:

src/screens/Profile.tsx
import core from '../core'
import navigation from '../navigation'

const Profile = (): JSX.Element => {
 const onPressLogout = async () => {
  try {
    // ...
    await core.user.logout()
    navigation.reset('Login')
  } catch (error) {
    // handle error
  } finally {
    // ...
  }
 }

 // ...
}

export default Profile
src/core.js
import Auth from 'my-auth-provider'

import userStore from '../stores/user-store'

const resetUser = userStore.getState().reset

export default {
  user: { 
    logout: async (email, password) => new Promise((resolve, reject) => {
      try { 
        await Auth.logout()
        // 👇
        resetUser()
        resolve(true)
      } catch (error) {
        reject(error)
      }
    })
  },
}hi

The data you want to use and display in your app is explicitly put inside the getState().data returned by and only there.

zfy conveniently exposes 2 methods for updating a store: & .

This is the method you'll be using the most as that's how data is being modified. Thanks to our use of Immer, you won't have to think about actions, reducers or even immutability. Just update your data, , Immer will take care of the rest. Example:

If you've enabled the provided middleware on a store, update() will automatically take care of saving data in a way that will allow rehydration to work without you having to do anything else.

If you're wondering how to do all of this but with multiple stores at once, look into the guide.

🤓
createaStore()
even with mutable update patterns
Handling multiple stores
update()
reset()
Handling multiple stores
createStore()
persist
log
customMiddlewares
persist