Skip to content

The mental model shift that actually matters ​

Vue updates the thing that changed. React re-runs the function and diffs the result. That one sentence explains half the confusion.

If you take only one idea from this whole tutorial - take this: React components are functions that return UI for the current state. Call them again with new state → new UI description → React patches the DOM.

Templates vs JSX ​

In Vue you write a template, and the compiler turns it into render code:

vue
<template>
  <button @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

In React you write the render yourself with JSX (which is still just function calls under the hood):

tsx
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  )
}

Comments for the code above:

  • No separate <template> / <script> blocks - it's one function
  • onClick instead of @click (DOM property names, camelCase)
  • {count} instead of - expression holes in JSX
  • You don't mutate count - you ask React to set a new value

Reactivity vs re-render ​

Vue tracks which properties you read and updates only the dependents. Feels surgical.

React doesn't track property access that way. When state changes, the component function runs again. Child components may re-render too (unless you memoize - and you shouldn't start there).

So instincts change:

Vue habitReact habit
Mutate a ref / reactive objectCall a setter with a new value
computed(() => …)Derive during render: const full = first + ' ' + last
watch(source, cb)Often useEffect - or better, derive / handle in the event
Fine-grained updatesWhole function re-runs; trust the reconciler

Composition API ≈ hooks (sort of) ​

If you live in <script setup>, hooks won't feel alien:

VueReact
ref(0)useState(0)
reactive({…})usually useState({…}) or multiple states
computedplain derived value in render
watch / watchEffectuseEffect (careful)
onMounteduseEffect(() => {…}, [])
provide / injectContext
composables (useX)custom hooks (useX)

The big difference: hooks have rules. Call them at the top level of the component (or another hook), every render, same order. No conditional useState. Vue composables are more chill about that.

Props and "slots" ​

Props are props. Named slots become… composition with children (and sometimes render props). We'll get there in the components chapter.

Default slot in Vue:

vue
<Card>
  <p>Hello</p>
</Card>

In React:

tsx
<Card>
  <p>Hello</p>
</Card>

Looks the same! Inside Card, you read props.children instead of <slot />.

What I'd keep in your head ​

  1. Component = function of state + props
  2. State updates are immutable (new value, not mutate-in-place)
  3. Effects are for syncing with the outside world - not for "when X changes, set Y" (that one is a classic trap)
  4. Your Vue Router / Pinia instincts still help - the libraries just have different names

Once that clicks, JSX syntax is just noise. Next we'll scaffold a Vite React app and poke at it.

Enjoy!