Skip to content

Vue.js Provide and Inject – Providing Things In Larger Contexts

I didn’t know that provide and inject existed until last week. Here was my situation: I was toying with creating a declarative <canvas> Vue.js library to render things. I wanted to create something that would work like so:

<v-canvas name="primary">
    <v-circle r="5" x="10" y="10" />
    <v-rectangle x="50" y="40" width="100" height="100" />
</v-canvas>

The idea was that the v-canvas element would create a canvas and provide (see what I did there?) a CanvasRenderingContext2D to each of it’s children.

I had trouble for a couple reasons…

  1. Each of the children of v-canvas needed the rendering context, and it was really hard to provide it to them using regular props
  2. I wanted to avoid scoped slots, because I shouldn’t have to have the user care about the rendering context. The fact that those children are children should be what guarantees they have the context.
  3. What can normally be added manually using React’s cloneElement was hard for me to figure out using Vue’s API and renderless functions.

I thought, there must be a better way!

Provide and Inject

And voila! Somewhere I ran into provide and inject. You can find the docs here: https://vuejs.org/v2/api/#provide-inject

In my v-canvas component I did something like this:

<template>
  <div>
    <canvas ref="canvas"></canvas>
    <slot />
  </div>
</template>

<script>
export default {
  name: "VueCanvas",
  data() {
    return {
      globalCanvas: {
        ctx: null
      }
    };
  },
  provide() {
    return {
      globalCanvas: this.globalCanvas
    }
  },
};
</script>

Suddenly, the context was available to any component that injects globalCanvas. It’s reactive. It’s easy. And I don’t have to wire up my children, and it doesn’t matter what child you are or how deep that child might be!

So here is an example of a component v-rectangle that injects this provided context:

const CvsRectangle = {
  name: 'CvsRectangle',
  props: {
    x: Number,
    y: Number,
    width: Number,
    height: Number,
  },
  inject: &#91;'globalCanvas'],
  methods: {
    renderToCanvas(ctx) {
      ctx.fillRect(this.x, this.y, this.width, this.height);
    }
  },
  render(createElement) {
    if (this.globalCanvas.ctx) {
      this.renderToCanvas(this.globalCanvas.ctx);
    }
  }
};

export default CvsRectangle;

Note that this is a special type of “renderless” component. Even if you haven’t encountered them before I think you can get the gist above. Here’s a great article on such things: https://adamwathan.me/renderless-components-in-vuejs/

Caveats

One, there is some weirdness to the fact that the actual prop you provide needs to be nested one layer. If it isn’t, apparently the provided bindings are not reactive? I wonder how this will work in Vue 3.

Two, the Vue docs are just as concerned as the React docs are about React.Context and warn about using it. They say…

provide and inject are primarily provided for advanced plugin / component library use cases. It is NOT recommended to use them in generic application code.

https://vuejs.org/v2/api/#provide-inject

Conclusion

Give it a shot! Let me know what you think. It would be nice to know if there are alternative patterns and solutions out there besides provide and inject

Leave a Reply

Your email address will not be published. Required fields are marked *