Vue for React Developers

Step by Step Component Conversion

Alex Riviere - https://alex.party

Senior Web Developer

@fimion@notacult.social

React is popular...

... because React is popular.

Let's convert a React component

to a Vue component

Todo App

  • Store an Array of todo items
  • Todo items can be marked as done and will disappear
  • toggle to show archived todo items
import { useState } from 'react'
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

function App() {

  const [todos, updateTodos] = useState([]);
  const [showArchived, updateShowArchived] = useState(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
}

export default App

App.jsx - React Version

Let's convert it to Vue!

import { useState } from 'react'
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

function App() {

  const [todos, updateTodos] = useState([]);
  const [showArchived, updateShowArchived] = useState(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
}

export default App

App.jsx - Update Imports

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

function App() {

  const [todos, updateTodos] = useState([]);
  const [showArchived, updateShowArchived] = useState(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
}

export default App

App.jsx - Update Imports

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

function App() {

  const [todos, updateTodos] = useState([]);
  const [showArchived, updateShowArchived] = useState(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
}

export default App

App.jsx - Update Definition

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const [todos, updateTodos] = useState([]);
  const [showArchived, updateShowArchived] = useState(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update Definition

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const [todos, updateTodos] = useState([]);
  const [showArchived, updateShowArchived] = useState(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - New State Mechanics

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - New State Mechanics

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }
  

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - create a computed value

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to use array methods

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to use array methods

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update marking todo as done

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update marking todo as done

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived)} 
               checked={showArchived} />
        Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Return a Render Function

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived)} 
               checked={showArchived} />
        Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Return a Render Function

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived)} 
               checked={showArchived} />
        Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to ref.value

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>showArchived.value = !showArchived.value)}
               checked={showArchived.value} />
        Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to ref.value

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived.value)}
               checked={showArchived.value} />
        Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to ref.value

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived.value)}
               checked={showArchived.value} />
        Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived.value)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived.value && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to ref.value

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived.value)}
               checked={showArchived.value} />
        Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived.value)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived.value && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to use a computed value

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived.value)}
               checked={showArchived.value} />
        Show Finished
      </label>
      <ul>
        {filteredTodos.value.map((todo, index) => (<li key={todo.id}>
          {!showArchived.value && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Update to use a computed value

What have we learned?

  • You can, in fact, use JSX templating with Vue
  • Vue uses reactive objects for state: ref and reactive
  • you can make reactive computed values using computed 
  • Vue returns a render function instead of just JSX
<script>
import {ref, reactive, defineComponent, computed} from "vue"

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent({
  setup() {
  
    const todos = reactive([]);
    const showArchived = ref(false);
    
    const filteredTodos = computed(()=>{
      return todos.filter((e)=>e.done===showArchived.value);
    })
    
    const addTodo = (item) => {
      const myTodo = newTodo(item);
      todos.push(myTodo);
    }
    
    const markDone = (todo)=>{
      todo.done = true;
    }
    
    const handleSubmit = (e) => {
      e.preventDefault();
      const formStuff = new FormData(e.target);
      addTodo(formStuff.get('item'));
      e.target.reset();
    }
    
    return {
      todos,
      showArchived,
      addTodo,
      markDone,
      handleSubmit,
      filteredTodos,
    }
  }
});
export default App
</script>
<template>
  <div class="App">
    <header class="App-header">
      <h1>My Todo List</h1>
    </header>
    <form @submit="handleSubmit" class="add-item">
      <label for="item">New Todo</label>
      <input type="text" name="item" id="item" />
      <button>ADD!</button>
    </form>
    <label>
    	<input type="checkbox"
        	   v-model="showArchived">
     	Show Finished</label>
    <ul>
      <li v-for="todo in filteredTodos" :key="todo.id">
      	<button v-if="!showArchived" @click="markDone(todo)">
      		<span aria-hidden="true">✔</span>
      		<span class="sr-only">Mark Done</span>
    	</button>
        {{todo.id}} - {{todo.item}}
    </li>
    </ul>
  </div>
</template>
<style scoped src="./App.css" />

App.vue - Vue Single File Component

Recap

  • React and Vue are more similar than you think.
  • Use the tool you like.
  • JSX is a templating language.

Thank You

More Resources

Nerando Johnson - Cassidy Williams

import { useState } from 'react'
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

function App() {

  const [todos, updateTodos] = useState([]);
  const [showArchived, updateShowArchived] = useState(false);
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    updateTodos([...todos, myTodo]);
  }

  const markDone = (todo)=>{
    todo.done = true;
    const index = todos.indexOf(todo);
    todos[index] = { ...todo };
    updateTodos([...todos]);
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
                    onChange={()=>updateShowArchived(!showArchived)} 
                    checked={showArchived} />
                    Show Finished
      </label>
      <ul>
        {todos.filter((e)=>e.done===showArchived)
        .map((todo, index) => (<li key={todo.id}>
          {!showArchived && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
}

export default App

App.jsx - React Version

/** @jsx h */
import {h, defineComponent, ref, reactive, computed} from "vue"
import './App.css'

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent(function() {

  const todos = reactive([]);
  const showArchived = ref(false);
  
  const filteredTodos = computed(()=>todos.filter((e)=>e.done===showArchived.value))
  
  const addTodo = (item) => {
    const myTodo = newTodo(item);
    todos.push(myTodo);
  }

  const markDone = (todo)=>{
    todo.done = true;
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    const formStuff = new FormData(e.target);
    addTodo(formStuff.get('item'));
    e.target.reset();
  }

  return ()=>(
    <div className="App">
      <header className="App-header">
        <h1>My Todo List</h1>
      </header>
      <form onSubmit={handleSubmit} className="add-item">
        <label htmlFor="item">New Todo</label>
        <input type="text" name="item" id="item" />
        <button>ADD!</button>
      </form>
      <label>
        <input type={'checkbox'} 
               onChange={()=>updateShowArchived(!showArchived.value)}
               checked={showArchived.value} />
        Show Finished
      </label>
      <ul>
        {filteredTodos.map((todo, index) => (<li key={todo.id}>
          {!showArchived.value && (<button onClick={()=>markDone(todo)}>
            <span aria-hidden="true">✔</span>
            <span className="sr-only">Mark Done</span>
          </button>)} {todo.id} - {todo.item}
        </li>))}
      </ul>
    </div>
  )
})

export default App

App.jsx - Vue Version

<script>
import {ref, reactive, defineComponent, computed} from "vue"

let todoCount = 0;
const newTodo = (item) => ({id:++todoCount, done:false, item });

const App = defineComponent({
  setup() {
  
    const todos = reactive([]);
    const showArchived = ref(false);
    
    const filteredTodos = computed(()=>{
      return todos.filter((e)=>e.done===showArchived.value);
    })
    
    const addTodo = (item) => {
      const myTodo = newTodo(item);
      todos.push(myTodo);
    }
    
    const markDone = (todo)=>{
      todo.done = true;
    }
    
    const handleSubmit = (e) => {
      e.preventDefault();
      const formStuff = new FormData(e.target);
      addTodo(formStuff.get('item'));
      e.target.reset();
    }
    
    return {
      todos,
      showArchived,
      addTodo,
      markDone,
      handleSubmit,
      filteredTodos,
    }
  }
});
export default App
</script>
<template>
  <div class="App">
    <header class="App-header">
      <h1>My Todo List</h1>
    </header>
    <form @submit="handleSubmit" class="add-item">
      <label for="item">New Todo</label>
      <input type="text" name="item" id="item" />
      <button>ADD!</button>
    </form>
    <label>
    	<input type="checkbox"
           	   @change="showArchived = !showArchived"
           	   :checked="showArchived">
     	Show Finished</label>
    <ul>
      <li v-for="todo in filteredTodos" :key="todo.id">
      	<button v-if="!showArchived" @click="markDone(todo)">
      		<span aria-hidden="true">✔</span>
      		<span class="sr-only">Mark Done</span>
    	</button>
        {{todo.id}} - {{todo.item}}
    </li>
    </ul>
  </div>
</template>
<style scoped src="./App.css" />

App.vue - Vue Single File Component

Vue for React Devs - Modern Web ATL 05-2023

By Alex Riviere

Vue for React Devs - Modern Web ATL 05-2023

  • 348