npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vue3-component-store

v0.0.16

Published

component store solution for vue 3

Downloads

1,106

Readme

Vue 3 Component Store

A simple, extensible component store for managing component logic, inspired by @ngrx/signals for angular.

ComponentStore is a stand-alone library that helps manage local/component state. It's an alternative to the reactive composables approach. The component store uses composables and dependency injection, along with an extensible mechanism that makes it powerful for reusing composables and extending store functionality in a simple and flexible way.

why component store and not composables ?

  1. No Conflict with Composables: The component store internally uses the composable approach and provide/inject. When using composables, reactive variables are recreated each time, preventing a standalone state for your component. If the state is outside the composable, it is shared across all instances, which can break the application. The component store solves this by maintaining a standalone state.
  2. Powerful and Easy to Use: The component store allows any composable to be used as an extension, enhancing its power and ease of use.
  3. Separation of View and Logic: By separating view and logic, you can divide your component into smaller parts, maintaining single responsibility. This eliminates the need to pass many props or events between the root component and its children, as the logic is handled in the component store. Both the root and child components can access the store to change state or execute functions like API requests or localStorage operations.
  4. shared logic between UI components : consider a complex component like datepicker that may need multiple different component for different UI or different functionality. so using component store you can easily share the logic between them which will make everything resuable and easy to add feature or even fixing a bug.

Powerful and Easy to Use: The component store allows any composable to be used as an extension, enhancing its power and ease of use.

Separation of View and Logic: By separating view and logic, you can divide your component into smaller parts, maintaining single responsibility. This eliminates the need to pass many props or events between the root component and its children, as the logic is handled in the component store. Both the root and child components can access the store to change state or execute functions like API requests or localStorage operations.

for better typescript support enable strictFunctionTypes in you tsconfig file

how to use

In counter.store.ts create a new component store using the componentStore function. This returns two functions to provide and use the instance in child components. Use the providedStore function only at the root component (each call creates a new instance).


export const [provideStore, useStore] = componentStore(
  withState<StoreStateI>({count: 0, items: [], title: 'Hello World'}),
  withState({name: 'John Doe'}),
  withComputed((store) => {
    return {
      doubleCount: computed(() => store.count * 2)
    }
  }),
  withComputed((store) => {
    return {
      tripleCount: computed(() => store.doubleCount * 3)
    }
  }),
  withMethods((store) => {
    return {
      increment() {
        store.count++
      },
      decrement() {
        store.count--
      },
      addItem(item: string) {
        store.items.push(item)
      },
      removeItem(index: number) {
        store.items.splice(index, 1)
      }
    }
  }),
  withHooks((store) => ({
    onUnmounted() {
      console.log('store unmounted')
      console.log(store)
    },
    onProvide() {
      console.log('store provided')
      console.log(store)
    },
    onMounted() {
      console.log('store mounted')

      console.log(store)
    }
  }))
)

Extensions

The component store can be extended by adding new functions or properties to the store. This is done by passing a function to the componentStore function. The function receives the store instance and returns an object with the new functions or properties. you also can use storeFeature function to combine several composables/extensions/features and create a single reusable store feature.


you always have access to properties/state or functions of previous features so the order of adding features are important


export function withRequestStatus() {
  return () => {
    const loading = ref(false)
    const error = ref('')
    const initializing = ref(true)
    function startLoading() {
      loading.value = true
      error.value = ''
    }
    function stopLoading() {
      loading.value = false
    }
    function setError(err: string) {
      error.value = err
    }
    function startInitializing() {
      initializing.value = true
    }
    function stopInitializing() {
      initializing.value = false
    }

    return {loading, error, initializing, startLoading, stopLoading, setError, startInitializing, stopInitializing}
  }
}

export function withEntity<T>() {
  return () => {
    const entity = ref<T | null>(null)
    const entities = ref<T[]>([])
    function setEntity(value: T| null) {
      entity.value = value
    }

    function setEntities(value: T[]) {
      entities.value = value
    }
    function resetAll() {
      entity.value = null
      entities.value = []
    }

    return {entity, entities, setEntity, setEntities, resetAll}
  }
}
export function newStoreFeature() {
  return storeFeature(
    withRequestStatus(),
    withEntity<UserInfo>(),
    (store) => {
      onMounted(async () => {
        const {users} = await getUsers()
        setData(users[0], users)
      })
      function setData(selecteduser: UserInfo, allUsers: UserInfo[]) {
        store.setEntity(selectedUser)
        store.setEntities(allUsers)
      }
      return {
        setData
      }
    }

  )
}

typescript support

component store has been written by typescript so you should not be worried about typescript support. but in some case it could be possible some features are dependant to each other. for example a spinner feature which is dependant to withRequestStatus feature because of "loading" state. in order to make sure there is already a feature which has provided the "loading" state you can define a type for store argument of spinner feature that expect a loading property like this:

export function withSpinner() {
  return (store: {loading: boolean}) => {
    console.log(store.loading)
    watch(() => store.loading, (isLoading) => {
      // trigger loading
    })
  }
}

in this case if you don't provide a feature before withSpinner that has provided "loading" property, you will face a typescript error. so awesome typescript support for dependant features.

Initial State

The component store can have an initial state that is passed as an argument to the provide function. This is useful when your store state is dependent on props or anything else. in order can pass initial state for your store you need to define initial state type to the first store feature argument like this:

const [provideUserStore, useUserStore] = componentStore(
  (store: {superAdmin: boolean,isLogin: boolean}) => {
    const allowAccess = computed(() => store.superAdmin || store.isLogin)
    return {allowAccess}
  },
  withRequestStatus(),
  withEntity<UserInfo>()
)

const userStore = provideUserStore({superAdmin: false, isLogin: false})

if you don't pass initial state to the provide function, typescript will throw an error.

Using Extensions

export const [provideUserStore, useUserStore] = componentStore(
  newStoreFeature(),
  (store) => {
    const userService = useUserService()
    const userInfo = ref<UserInfo|null>(null)

    onMounted(async () => {
      store.startInitializing()
      try {
        const user = await userService.getUser()
        userInfo.value = user
      } catch (err) {
        store.setError(err)
      } finally {
        store.stopInitializing()
      }
    })
    async function updateUser() {
      store.startLoading()
      try {
        const user = await userService.updateUser(userInfo.value)
        userInfo.value = user
      } catch (err) {
        store.setError(err)
      } finally {
        store.stopLoading()
      }
    }
    return {userInfo}
  })
)

how to use store

you can simply create the store instance for your component by calling the provideUserStore function

root component

<script setup lang="ts">
  import {provideUserStore} from '@/user.store'
  const store = provideUserStore()
</script>

<template>
  <div v-if="store.initializing">initializing...</div>
  <div v-else-if="store.error">{{store.error}}</div>
  <div v-else-if="store.userInfo">
    <input v-model="store.userInfo.name" />
    <UserAddress />
    <button @click="store.updateUser()" disabled="store.loading"> Update </button>
</div>

child components

for children component you must use the useUserStore function to access the store instance

<script setup lang="ts">
  import {useUserStore} from '@/user.store'
  const store = useUserStore()
</script>

<template>
  <div class"address-form">
    <input v-model="store.userInfo.country" />
    <input v-model="store.userInfo.city" />
    <input v-model="store.userInfo.street" />
    <input v-model="store.userInfo.address" />
  </div>
</template>