Search code examples
vue.jsvuejs3vue-component

Why should one use provide/inject in Vue 3


I have an application where one of its components is responsible for creating and managing a tree. Other components in this application sometimes need the tree data.
One of the available solutions for using the tree data in other components is to use provide/inject.
I wanted to see what the necessity of using this method is, given that I can access the tree data and see changes by using the class below and calling the useTreeIndex function in any component

import { createGlobalState } from '@vueuse/core'
import { isUndefined } from '@sindresorhus/is'
import type { ISimpleTreeActionable } from '@/types/baseModels'
import { SimpleTreeModel } from '@/types/baseModels'

export const useSelectedNode = createGlobalState(
  () => {
    const simpleTreeModelStored = reactive<SimpleTreeModel>(new SimpleTreeModel())

    return { simpleTreeModelStored }
  },
)

const treeData = reactive<ISimpleTreeActionable[]>([])
const treeIndex = reactive<Record<number, ISimpleTreeActionable>>({})

export function useTreeData() {
  return treeData
}
export function useTreeIndex() {
  return treeIndex
}

export function addNode(nodeItem: ISimpleTreeActionable): boolean {
  if (nodeItem.parentId === -1)
    return false

  if (isUndefined(treeIndex[nodeItem.parentId].children))
    treeIndex[nodeItem.parentId].children = []
  treeIndex[nodeItem.parentId].children?.push(nodeItem)
  treeIndex[nodeItem.id] = nodeItem

  return true
}

I use the following code in another component and have access to the complete tree data.

const treeIndexStore = useTreeIndex()

What is the best method for using and working with data that is needed and used in several other components


Solution

  • This is a simple implementation of global state described in the documentation. A lot of times it works, and there may be no need to look further. It's beneficial to wrap it with composable-like functions like useTreeData because this allows to change the implementation later without refactoring the application.

    provide/inject additionally do this:

    • keep state local to the component hierarchy

    • provide a mechanism for dependency injection based on the component hierarchy (beneficial for testing, not so much with Jest/Vitest module mocking)

    Given there are several sibling component trees, they can have independent states if necessary, or a component can override parent state for its children.

    The downside of provide/inject strictly follows the rules of composables and can be only used only in the body of setup, this limits the usage in non-component context (services, router hooks, etc).

    The upside is that it naturally prevents the state from leaking outside root component. This would be a major problem for a simple global state because it's incompatible with SSR; each application instance on server side should have its own state, which is impossible when a state is a singleton.

    Pinia is commonly considered a good choice for global state, particularly it doesn't have these drawbacks. It can be used inside and outside the components because it doesn't rely on provide/inject, yet Pinia instance is tied to application instance and safe use in SSR context.