A React Developer's Guide to Vue.js


After more than four years of building apps with React, I decided to learn Vue.js. This guide is for React developers who want to understand Vue through familiar concepts. I'll walk through similarities, key differences, and mental shifts to help you pick up Vue faster.
1. Component Syntax: JSX vs Template
React uses JSX, mixing JavaScript and HTML in the same file.
Vue uses
<template>
for markup,<script>
for logic, and optional<style>
.
React:
function Button({ label }) {
return <button>{label}</button>;
}
Vue:
<template>
<button>{{ label }}</button>
</template>
<script setup>
const props = defineProps(['label'])
</script>
2. State Management: useState
vs ref
and reactive
ref
is likeuseState
for primitive valuesreactive
is better for objects/arrays
React:
const [count, setCount] = useState(0)
Vue:
const count = ref(0)
For objects:
const user = reactive({ name: 'Neta' })
You update Vue state directly:
count.value++
user.name = 'Ada'
3. Derived State: useMemo
vs computed
Both are used for memoized values.
React:
const total = useMemo(() => price * quantity, [price, quantity])
Vue:
const total = computed(() => price.value * quantity.value)
Vue tracks dependencies automatically.
4. Side Effects: useEffect
vs watch
React:
useEffect(() => {
fetchData()
}, [id])
Vue:
watch
lets you track changes to state
watch(id, () => {
fetchData()
})
5. Props and Events
React:
Props passed as JSX attributes
Event handlers passed down as functions
Vue:
Props declared using
defineProps()
Emits handled with
emit()
Child.vue:
<script setup>
const emit = defineEmits(['submit'])
const handleClick = () => emit('submit')
</script>
Parent.vue:
<Child @submit="doSomething" />
6. Children vs Slots
React:
props.children
Vue:
<slot />
React:
function Layout({ children }) {
return <div>{children}</div>
}
Vue:
<template>
<div><slot /></div>
</template>
Named slots are supported too:
<slot name="header" />
7. Conditional Rendering
React:
{isLoggedIn ? <Dashboard /> : <Login />}
Vue:
<Dashboard v-if="isLoggedIn" />
<Login v-else />
8. List Rendering
React:
{items.map(item => <li key={item.id}>{item.name}</li>)}
Vue:
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
9. Forms and Two-Way Binding
React:
<input value={name} onChange={e => setName(e.target.value)} />
Vue:
<input v-model="name" />
10. Global State: Redux Toolkit vs Pinia
React: Redux, Context API Vue: Pinia (official store library)
Pinia example:
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ name: 'Ada' }),
actions: {
setName(newName) {
this.name = newName
},
},
})
11. Data Fetching: React Query vs Vue Query
React: React Query (useQuery
) Vue: Vue Query (same API)
import { useQuery } from '@tanstack/vue-query'
const { data, isLoading } = useQuery({
queryKey: ['jobs'],
queryFn: () => axios.get('/api/jobs'),
})
12. Making API Calls
React:
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/api/users')
const data = await res.json()
setUsers(data)
}
fetchData()
}, [])
Vue:
onMounted(async () => {
const res = await fetch('/api/users')
const data = await res.json()
users.value = data
})
13. Context API vs Provide/Inject
React:
React uses Context to share state across components.
const ThemeContext = createContext()
function App() {
return (
<ThemeContext.Provider value="dark">
<Child />
</ThemeContext.Provider>
)
}
function Child() {
const theme = useContext(ThemeContext)
return <div>{theme}</div>
}
Vue:
provide
shares valuesinject
receives them in child components
<!-- Parent.vue -->
<script setup>
import { provide } from 'vue'
provide('theme', 'dark')
</script>
<!-- Child.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
</script>
<template>
<div>{{ theme }}</div>
</template>
14. Custom Hooks (React) vs Composables (Vue)
React:
function useCounter() {
const [count, setCount] = useState(0);
return { count, increment: () => setCount(c => c + 1) };
}
Vue:
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
Usage:
<script setup>
import { useCounter } from './counter.js'
const { count, increment } = useCounter()
</script>
<template>
<p>{{count}}</p>
<button @click="increment">Add</button>
</template>
15. DevTools
Vue has its own Vue Devtools (Chrome/Firefox)
Shows reactive values, component hierarchy, and Pinia stores
Like React Devtools but built specifically for Vue’s reactivity model
Final Thoughts
If you're comfortable with React, learning Vue won't be hard. Vue’s syntax is simpler, and its reactivity system makes many things more intuitive. ref
, computed
, and watch
feel like natural extensions of hooks. Once you get past the template
syntax, everything else starts to click.
Use this guide as your map. Start building.
Subscribe to my newsletter
Read articles from Chineta Adinnu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Chineta Adinnu
Chineta Adinnu
Hi, I’m Chineta Adinnu! I’m a frontend developer with a passion for creating dynamic and user-friendly web experiences. On this blog, I share my journey in frontend development, from learning new technologies to implementing them in real projects. I dive into frameworks like React and Next.js, explore state management with Redux Toolkit, and offer practical tips based on my hands-on experience. My goal is to provide valuable insights and tutorials that can help others navigate the ever-evolving world of web development. Join me as I document my learning process, share challenges and solutions, and offer guidance on the latest frontend technologies. If you’re interested in tech or looking for practical advice, you’re in the right place! Feel free to connect if you have questions or want to discuss tech! Check out some of my articles on Medium: https://medium.com/@chinetaadinnu."