Vue and TSX ?! 🤯
Tags: vue, typescript, javascript, react
Published: 2023-05-20 19:28:40

Many developers appreciate React and its Functional Components (FCs) due to the ease of building "multi-piece" components using JSX (or TSX).  While some may assume that JSX is unavailable in Vue, in fact, it is perfectly possible to ditch Vue’s conventional Single File Component pattern (SFC) and use JSX or TSX to build components that share the same context like you would with React. In this article you will learn about the pro’s and con’s of using SFCs or using Vue with JSX and how to build a complex components with a used context by using JSX.

**So let’s dive right in!**

## What are Single File Components (SFCs)?

As the name might already give away, SFCs are components that allow us to write a components by using only a single file for the **logic, style and template** of the component. This is a pattern that is used by the two frameworks Vue and Svelte, where SFCs are saved with the extensions .vue and .svelte, respectively. The structure of a SFC in Vue might look as follows:

```html
<script setup>
  //component logic goes here
</script>
<template>
  <!-- html goes here -->
</template>
<style>
  //css styling goes here
</style>
```

This certainly  is great, when you are building a component, where all component logic is contained within the same file and exposes the following main benefits on the developer experience (DX):

- The component has a clear and maintainable structure that is quite forward (very close to standard HTML)

- Using scoped styles you can make sure that your CSS styling only applies to one single component (however, this is often not used in practice)

- There is a clear separation of concerns (logic of different components is split to different files)

However, for building components that use a shared context (like for example a dropdown-menu or a accordion), the SFC pattern might seem less convenient. In my opinion, this is where React and functional components (FCs) with JSX perform better. [This article might interest you, if you want to read more about why Ryan Carniato is not a fan of SFCs.](https://dev.to/ryansolid/why-i-m-not-a-fan-of-single-file-components-3bfl)

---

## What is JSX?
JSX stands for JavaScript XML. It is a way of writing HTML-like code within Javascript code. When used within React, it is also possible to express the logic and html within the same file. An example for a FC component  within a JSX file could look as follows:

```javascript
const MyComponent = () => {  
   //logic is expressed here
   return <div>/* html goes here */</div>;
};

const MyOtherComponent = () => {  
   //logic is expressed here
   return <div>/* html goes here */</div>;
};

export { MyComponent, MyOtherComponent };
```

As you might notice, with this file it is possible to declare two different components and export them. This can for example come in very handy, when you want to share a state between nested components and do not want to pass it via the component props (which in Vue is called Prop Drilling). If you have been using React before, the hook **useContext()** might be familiar to you. Using this hook you can access the state defined in a parent component, in any nested Child component. [Here you can read more about how to share state between components in React.](https://react.dev/learn/sharing-state-between-components)

---

## But... Also Vue can do the trick! 

While the default of using SFCs within .vue files, might in most cases be the right fit, sometimes it might be more beneficial to resort to an alternative solution (for example when you are building a component library). In the following we are going to look at an example where we define a three components _Parent, Child and DeepChild_ in the same .tsx file. The components _Parent_ and _DeepChild_ share the same value for count and by clicking a button in the component grand-child we want to update this variable.

```javascript
import { defineComponent, provide, inject, Ref, ref } from "vue";

const Parent = defineComponent({
  name: "Parent",
  setup(_, { slots }) {
    // global state count
    const count = ref(0);

    //callback to update count
    const updateCount = () => {
      count.value++;
    };

    // provide count and updateCount to all children
    provide("count", {
      count,
      updateCount,
    });

    return () => (
      <div>
        <span>Parent: {count.value}</span>
        {slots.default?.()}
      </div>
    );
  },
});

const Child = defineComponent({
  name: "Child",
  setup(_, { slots }) {
    // count and updateCount not accessed here
    return () => <div>{slots.default?.()}</div>;
  },
});

const DeepChild = defineComponent({
  name: "DeepChild",
  setup() {
    // count and updateCount injected in DeepChild
    const { count, updateCount } = inject("count") as {
      count: Ref<number>;
      updateCount: () => void;
    };

    return () => (
      <button onClick={() => updateCount()}>DeepChild: {count.value}</button>
    );
  },
});

export { Parent, Child, DeepChild };
```

Using the functions _provide()_ and _inject()_ we can make the state and an update function globally available to all components by binding them to a key count. When you are coming from React, this might already familiar to you as its quite similar to providing `[state, setState] = useState(count)` to other components via `useContext()`. 

Now we can import the three components _Parent, Child and DeepChild_ where we need them.

```javascript
<script setup lang="ts">
import {
  Parent, 
  Child,
  DeepChild,
} from "../components/nested-component";
</script>
<template>
 <Parent>
    <Child>
      <DeepChild />
    </Child>
 </Parent>
</template>
```

And the result looks as follows. We can update count from within the DeepChild component without having to pass it through the intermediate component _Child_ and Prop Drilling.

![Clicking button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cta1ztbvhh7b2nd6vlq8.gif)

Using **provide/inject** is not exclusive to TSX component, however, it seems a lot more intuitive and maintainable having this logic defined in the same file. Here are some ideas, where you might find using TSX useful:

- Deeply nested components
- recursive components 
- Headless components and primitives

---

## That's all folks! 

What do you think? Do you sometimes use TSX or do you only stick to Vue's SFCs? Where could you imagine could it be applied?

I hope you liked this article and find it useful. Thanks for reading! ❤️

## References

- [https://vuejs.org/guide/components/provide-inject.html](https://vuejs.org/guide/components/provide-inject.html)

- [https://vuejs.org/guide/typescript/overview.html](https://vuejs.org/guide/typescript/overview.html)

- [https://vuejs.org/guide/extras/render-function.html](https://vuejs.org/guide/extras/render-function.html)

- [https://react.dev/learn/sharing-state-between-components](https://react.dev/learn/sharing-state-between-components)

**P.S.: Thanks to Chat GPT for proofreading this article 😘**