<!-- Counter.vue -->
<template>
<button>{{ count }}</button>
</template>
<script setup>
const count = 0;
</script>
// Counter.jsx
export default function Counter() {
const count = 0;
return <button>{count}</button>
}
<!-- Counter.vue -->
<template>
<button>
{{ count }}
</button>
</template>
<script setup lang="ts">
type Props = {
count: number;
}
const props = defineProps<Props>();
</script>
// Counter.tsx
type Props = {
count: number;
}
function Counter(props: Props) {
return (
<button>
{props.count}
</button>
);
}
export default Counter;
<!-- CounterUser.vue -->
<template>
<div class="p-4">
<Counter :count="count" />
</div>
</template>
<script setup lang="ts">
import Counter from "./Counter.vue"
const count = 0;
</script>
// CounterUser.tsx
import Counter from "./Counter.jsx";
function CounterUser() {
const count = 0;
return (
<div className="p-4">
<Counter count={count} />
</div>
);
}
export default CounterUser;
<!-- Counter.vue -->
<template>
<button @click="count++">
{{ count }}
</button>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
// Counter.jsx
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button
onClick={() => setCount(count + 1)}
>
{count}
</button>
);
}
<!-- Counter.vue -->
<template>
<button @click="count++">
{{ doubled }}
</button>
</template>
<script setup>
import { ref, computed } from 'vue';
const count = ref(0);
const doubled = computed(() => count * 2);
</script>
// Counter.jsx
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
const doubled = count * 2;
return (
<button
onClick={() => setCount(count + 1)}
>
{doubled}
</button>
);
}
<template>
<button>
{{ count }}
</button>
<div v-if="count == 69">Nice</div>
<div v-else>Meh</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<button
onClick={() => setCount(count + 1)}
>
{count}
</button>
{count == 69 ? (
<div>Nice</div>
) : (
<div>Meh</div>
)}
</>
);
}
<template>
<SkeletonLoader
v-if="isLoading"
/>
<ErrorOverlay
v-else-if="isError"
:message="error.message"
/>
<ProductList
v-else
:products="data"
/>
</template>
<script setup lang="ts">
const { data, isLoading, isError } =
useProducts();
</script>
function Counter() {
const { data, isLoading, isError } =
useProducts();
if (isLoading) {
return <SkeletonLoader />;
}
if (isError) {
return (
<ErrorOverlay
message={error.message}
/>
);
}
return <ProductList products={data} />;
}
<template>
<div
v-for="product in products"
:key="product.id"
>
{{ product.name }}
</div>
</template>
<script setup lang="ts">
defineProps<{products: Product[]}>();
</script>
export default function ProductList({
products,
}: {
products: Product[];
}) {
return (
<>
{products.map((product) => (
<div key={product.id}>
{product.name}
</div>
))}
</>
);
}
<template>
<input type="number" v-model="count">
</template>
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(0);
</script>
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState();
return (
<input
value={count}
onChange={(e) =>
setCount(e.target.value)
}
/>
);
}
<!-- Counter.vue -->
<template>
<button @click="emit('increment')">
{{ count }}
</button>
</template>
<script setup lang="ts">
defineProps<{count: number}>()
defineEmits<{increment: []}>()
</script>
<!-- CounterUser.vue -->
<template>
<Counter
:count="count"
@increment="increment"
>
</template>
<script setup lang="ts">
const count = ref(0);
const increment = () => count.value++;
</script>
export function Counter(props: {
count: number;
onIncrement: () => void;
}) {
return (
<button onClick={props.onIncrement}>
{count}
</button>
);
}
export function CounterUser() {
const [count, setCount] = useState(0);
return (
<Counter
count={count}
onIncrement={() => setCount(count + 1)}
/>
);
}
<template>
<input type="number" ref="input">
</template>
<script setup lang="ts">
import { templateRef } from 'vue';
const inputRef = templateRef("input");
onMounted(() => {
inputRef.value.scrollIntoView();
})
</script>
import { useRef, useEffect } from "react";
export default function AutoScroll() {
const inputRef =
useRef<HTMLInputElement>();
useEffect(() => {
inputRef.current.scrollIntoView();
}, []);
return <input ref={inputRef} />;
}
<!-- Layout.vue -->
<template>
<div>
<slot />
</div>
</template>
<!-- Parent.vue -->
<template>
<Layout>Hello from parent</Layout>
</template>
import type { ReactNode } from "react";
export const Layout = ({
children,
}: {
children: ReactNode;
}) => {
return <div>{children}</div>;
};
export const Parent = () => (
<Layout>Hello from parent</Layout>
);
<!-- Layout.vue -->
<template>
<main>
<slot name="header" />
<slot name="footer" />
</main>
</template>
<!-- Parent.vue -->
<template>
<Layout>
<template #header>
<header>
Header
</header
</template>
<template #footer>
<footer>
Footer
</footer>
</template>
</Layout>
</template>
import type { ReactNode } from "react";
export const Layout = ({
header,
footer,
}: {
header: ReactNode;
footer: ReactNode;
}) => {
return (
<main>
{header}
{footer}
</main>
);
};
export const Parent = () => (
<Layout
header={<header>Header</header>}
footer={<footer>Footer</footer>}
/>
);
<!-- Layout.vue -->
<template>
<div>
<slot :count="1" />
</div>
</template>
<!-- Parent.vue -->
<template>
<Layout>
<template v-slot="props">
<div>
{{ props.count }}
</div>
</template>
</Layout>
</template>
import type { ReactNode } from "react";
export const Layout = ({
children,
}: {
children: (count: number) => ReactNode;
}) => {
return <div>{children(1)}</div>;
};
export const Parent = () => (
<Layout>
{(count) => <div>{count}</div>}
</Layout>
);
<template>
<button>
{{ count ?? "loading..." }}
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const count = ref<number>();
async function fetchCount() {
count.value = await fetch("/count");
}
onMounted(fetchCount);
</script>
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState();
useEffect(() => {
fetch("/count")
.then((count) => setCount(count));
}, []);
return (
<button>
{count ?? "loading..."}
</button>
);
}
export default Counter;
<template>
<button>
{{ count }}
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(0);
const intervalId = ref<number>();
function startCounter() {
intervalId.value = setInterval(
() => count.value++, 1000
);
}
onMounted(startCounter);
onUnmounted(
() => clearInterval(intervalId.value)
);
</script>
import {
useState,
useEffect,
} from "react";
export default function Counter() {
const [count, setCount] = useState(0);
const increment = setCount(count + 1);
useEffect(() => {
const intervalId = setInterval(
increment,
500
);
return () =>
clearInterval(intervalId);
}, []);
return <button>{count}</button>;
}
<template>
<button @click="count++">
{{ count }}
</button>
</template>
<script setup lang="ts">
import { ref, onUpdated } from 'vue';
const count = ref(0);
onUpdated(() => console.log("Updated!"))
</script>
import { useState, useEffect } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
const increment = setCount(count + 1);
useEffect(() => console.log("Updated"));
return (
<button onClick={increment}>
{count}
</button>
);
}
<!-- Counter.vue -->
<template>
<button @click="count++">
{{ doubled ?? "loading..." }}
</button>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const count = ref(0);
const doubled = ref<number>();
async function fetchDouble() {
fetch(`/double/${count}`)
.then(double => doubled.value = double);
}
watch(count, fetchDouble);
</script>
import { useState, useEffect } from "react";
export default function Counter() {
const [count, setCount] = useState();
const [doubled, setDoubled] = useState();
function fetchDouble() {
fetch(`/double/${count}`).then(
(double) => setDoubled(double)
);
}
useEffect(fetchDouble, [count]);
return (
<button
onClick={() => setCount(count + 1)}
>
{doubled ?? "loading..."}
</button>
);
}
// useMousePosition.js
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
onMounted(() => {
window.addEventListener(
"mousemove",
update
);
});
onUnmounted(() => {
window.removeEventListener(
"mousemove",
update
);
});
return { x, y };
}
// useMousePosition.js
export function useMousePosition() {
const [x, setX] = useState(0);
const [y, sety] = useState(0);
const update = (e) => {
setX(e.pageX);
sety(e.pageY);
};
useEffect(() => {
window.addEventListener(
"mousemove",
update
);
return () => {
window.removeEventListener(
"mousemove",
update
);
};
}, []);
return { x, y };
}
export default function Section() {
return (
<p>Lorem ipsum</p>
<p>Dolor sit ames</p>
)
}
export default function Section() {
return (
<>
<p>Lorem ipsum</p>
<p>Dolor sit ames</p>
</>
)
}
🚫
✅
must return a single JSX element
export default function Section() {
return (
<>
<label
for="username-input"
class="text-gray-700"
>
Username
</label>
<input
type="text"
id="username-input"
/>
</>
);
}
export default function Section() {
return (
<>
<label
htmlFor="username-input"
className="text-gray-700"
>
Username
</label>
<input
type="text"
id="username-input"
/>
</>
);
}
✅
🚫
renamed html attributes
export default function ProductPage() {
const productId = useParams();
if (!productId) {
redirect("/404");
}
const product = useProduct(productId);
return (
<>
// ...
</>
);
}
export default function ProductPage() {
const productId = useParams();
const product = useProduct(productId ?? "");
if (!product) {
redirect("/404");
}
return (
<>
// ...
</>
);
}
✅
🚫
hook rules
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>
+2
</button>
</div>
);
}
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>
+2
</button>
</div>
);
}
✅
🚫
scope capture
function ProfileEditor() {
const [profile, setProfile] =
useState({ name: "Alice" });
const updateName = () => {
profile.name = "Bob";
// same reference, won't rerender!
setProfile(profile);
};
return (
<div>
<p>Name: {profile.name}</p>
<button onClick={updateName}>
Change Name to Bob
</button>
</div>
);
}
function ProfileEditor() {
const [profile, setProfile] =
useState({ name: "Alice" });
const updateName = () => {
// new object, new reference
setProfile({...profile, name: "Bob"});
};
return (
<div>
<p>Name: {profile.name}</p>
<button onClick={updateName}>
Change Name to Bob
</button>
</div>
);
}
✅
🚫
state update based on reference not value