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

vue-atoms

v0.2.0

Published

Better type-safe provide() and inject() for Vue

Downloads

103

Readme

vue-atoms

Note: This is pre-1.0 so feedback welcome, and API is not yet stable. Right now, this is designed as a drop-in, type-safe replacement to provide() and inject().

Installation

npm install vue-atoms

Usage

import { atom } from 'vue-atoms'

export const counterAtom = atom(0)
<script setup lang="ts">
import { inject } from 'vue-atoms'
import { counterAtom } from './atoms'

const counter = inject(counterAtom)
</script>

<template>
  <div>{{ counter }}</div>
  <button @click="counter++">Increment</button>
</template>

Explanation

The Problem with provide() and inject()

The provide() and inject() Vue API is one of the more awkward parts, especially when it comes to TypeScript and type-safety.

To get proper types (sort of), Vue asks you to do a few things.

  1. Use a symbol as a key.
  2. Type your symbol as InjectionKey<{type}>

This looks like:

import { provide } from 'vue'
export const key = Symbol() as InjectionKey<string>
provide(key, 'foo')

When consuming the value, to get the type, you then do:

import { inject } from 'vue'
import { key } from './injection-keys'

const foo = inject(key)

This causes a number of side-effects / problems.

For one, the value of foo is not guaranteed, meaning that the value is always returned as T | undefined even if you gave an explicit type for T. Vue tells you to workaround this by using as again like:

const foo = inject(key) as string

This can lead to unexpected runtime errors in your code, because as essentially circumvents any type-checking. Meaning, the official Vue documentation for typing provide / inject is both an abuse of the type system, and represents poor TypeScript practices.

Furthermore, because Vue is abusing the type system, your symbol key is no longer recognized as a symbol. This can lead to type errors when using Vue Test Utils, like so:

mount(MyComponent, {
  global: {
    provide: {
      // Throws TypeScript error: "A computed property name must be of type 'string', 'number', 'symbol', or 'any'"
      [key]: 'value'
    }
  }
})

A better type-safe provide() and inject() for Vue

Inspired by React Context and Jotai, vue-atoms creates small pieces of state called "atoms", which have a default value, and are typed either implicitly by the value, or by an explicit type.

First, you create an atom:

import { ref } from 'vue'
import { atom } from 'vue-atoms'

export const counterAtom = atom(ref(0))

In your Vue component, you inject the value like you normally would. However, the atom does not need an explicit provider, and if one isn't found, will use the default value.

<script setup lang="ts">
import { inject } from 'vue-atoms'
import { counterAtom } from './atoms'

// type is Ref<number> with a value of `0`
const counter = inject(counterAtom)
</script>

<template>
  <div>{{ counter }}</div>
  <button @click="counter++">Increment</button>
</template>

If you wish to provide a new value for part of the component tree, you can do so like the following:

<script setup lang="ts">
import { ref } from 'vue'
import { provide } from 'vue-atoms'
import { counterAtom } from './atoms'

// The value for `provide` is type-checked to be of the same type as your atom.
provide(counterAtom, ref(100))
</script>

<template>
  <Consumer />
</template>

You can also compute values from atoms to provide for deeper consumers:

<script setup lang="ts">
import { inject, provide } from 'vue-atoms'
import { computed } from 'vue'
import { counterAtom } from './atoms'

const counter = inject(counterAtom)
const computedCounter = computed(() => counter.value + 10)
provide(counterAtom, computedCounter)
</script>

Atoms are symbols!

This will now work:

// ... test
mount(MyComponent, {
  global: {
    provide: {
      [counterAtom]: ref(10)
    }
  }
})

Demo

See: https://stackblitz.com/edit/vitejs-vite-baobw8?file=src%2FApp.vue