This document shows how to rewrite examples from the https://react.dev/learn/ documentation with ivi API.
React:
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
ivi:
import { htm } from '@ivi/htm';
const Profile = () => htm`
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
`;
const Gallery = () => htm`
<section>
<h1>Amazing scientists</h1>
${Profile()}
${Profile()}
${Profile()}
</section>
`;
export default Gallery;
React:
import Profile from './Profile.js';
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
ivi:
import { htm } from '@ivi/htm';
import Profile from './Profile.js';
const Gallery = () => htm`
section
h1 'Amazing scientists'
${Profile()}
${Profile()}
${Profile()}
`;
export default Gallery;
React:
export default function TodoList() {
return (
<>
<h1>Hedy Lamarr's Todos</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>Invent new traffic lights</li>
<li>Rehearse a movie scene</li>
<li>Improve spectrum technology</li>
</ul>
</>
);
}
ivi:
import { htm } from '@ivi/htm';
const TodoList = () => htm`
<h1>Hedy Lamarr's Todos</h1>
<img
class"photo"
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
/>
<ul>
<li>Invent new traffic lights</li>
<li>Rehearse a movie scene</li>
<li>Improve spectrum technology</li>
</ul>
`;
export default TodoList;
React:
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};
export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}
ivi:
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};
const TodoList = () => htm`
<div
~background-color=${person.theme.backgroundColor}
~color=${person.theme.color}
>
<h1>${person.name}'s Todos</h1>
<img
class="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
`;
export default TodoList;
React:
import { getImageUrl } from './utils.js'
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
ivi:
import { getImageUrl } from './utils.js'
const Profile = () => (
Card(
Avatar({
size: 100,
person: {
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
},
}),
)
);
export default Profile;
React:
function Item({ name, isPacked }) {
return (
<li className="item">
{name} {isPacked && '✔'}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride's Packing List</h1>
<ul>
<Item
isPacked={true}
name="Space suit"
/>
<Item
isPacked={true}
name="Helmet with a golden leaf"
/>
<Item
isPacked={false}
name="Photo of Tam"
/>
</ul>
</section>
);
}
ivi:
import { htm } from "@ivi/htm";
const Item = ({ name, isPacked }) => htm`
<li class="item">
${name} ${isPacked && '✔'}
</li>
`;
const PackingList = () => htm`
<section>
<h1>Sally Ride's Packing List</h1>
<ul>
${Item({ isPacked: true, name: 'Space suit' })}
${Item({ isPacked: true, name: 'Helmet with a golden leaf' })}
${Item({ isPacked: false, name: 'Photo of Tam' })}
</ul>
</section>
`;
export default PackingList;
React:
import { people } from './data.js';
import { getImageUrl } from './utils.js';
export default function List() {
const listItems = people.map(person =>
<li key={person.id}>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
return (
<article>
<h1>Scientists</h1>
<ul>{listItems}</ul>
</article>
);
}
ivi:
import { List } from 'ivi';
import { htm } from '@ivi/htm';
import { people } from './data.js';
import { getImageUrl } from './utils.js';
const ScientistsList = () => htm`
<article>
<h1>Scientists</h1>
<ul>
${List(people, (person) => person.id, (person) => htm`
<li>
<img
src=${getImageUrl(person)}
alt=${person.name}
/>
<p>
<b>${person.name}:</b>
\v ${person.profession}
\v known for ${person.accomplishment}
</p>
</li>
`)}
</ul>
</article>
`;
export default ScientistsList;
React:
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}
ivi:
import { htm } from '@ivi/htm';
const Cup = ({ guest }) => htm`
<h2>Tea cup for guest #${guest}</h2>
`;
const TeaSet = () => [
Cup({ guest: 1 }),
Cup({ guest: 2 }),
Cup({ guest: 3 }),
];
export default TeaSet;
React:
export default function App() {
return (
<Toolbar
onPlayMovie={() => alert('Playing!')}
onUploadImage={() => alert('Uploading!')}
/>
);
}
function Toolbar({ onPlayMovie, onUploadImage }) {
return (
<div>
<Button onClick={onPlayMovie}>
Play Movie
</Button>
<Button onClick={onUploadImage}>
Upload Image
</Button>
</div>
);
}
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
ivi:
import { component, useState } from 'ivi';
import { htm } from '@ivi/htm';
const App = component((c) => {
const onPlayMovie = () => alert('Playing!');
const onUploadImage = () => alert('Uploading!');
return () => ToolBar({ onPlayMovie, onUploadImage });
});
export default App;
const Toolbar = ({ onPlayMovie, onUploadImage }) => htm`
<div>
${Button({ onClick: onPlayMovie, children: 'Play Movie' })}
${Button({ onClick: onUploadImage, children: 'Upload Image' })}
</div>
`;
const Button = ({ onClick, children }) => htm`
<button @click=${onClick}>
${children}
</button>
`;
React:
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
{showMore && <p>{sculpture.description}</p>}
<img
src={sculpture.url}
alt={sculpture.alt}
/>
</>
);
}
ivi:
import { component, useState } from 'ivi';
import { htm } from '@ivi/htm';
import { sculptureList } from './data.js';
const Gallery = () => {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
return () => {
const index = index();
const showMore = showMore();
const sculpture = sculptureList[index];
return htm`
<button @click=${handleNextClick}>Next</button>
<h2>
<i>${sculpture.name}</i>
\v by ${sculpture.artist}
</h2>
<h3>(${index + 1} of ${sculptureList.length}</h3>
<button @click=${handleMoreClick}>
${showMore ? 'Hide' : 'Show'} details
</button>
${showMore && htm`<p>${sculpture.description}</p>`}
<img
src=${sculpture.url}
alt=${sculpture.alt}
/>
`;
};
}
export default Gallery;
React:
console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!
ivi:
Like regular javascript variables, ivi state is updated immediately.
console.log(count()); // 0
setCount(count() + 1); // Request a re-render with 1
console.log(count()); // 1
React:
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
setPerson({
...person,
name: e.target.value
});
}
function handleTitleChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
title: e.target.value
}
});
}
function handleCityChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
city: e.target.value
}
});
}
function handleImageChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
image: e.target.value
}
});
}
return (
<>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<label>
Title:
<input
value={person.artwork.title}
onChange={handleTitleChange}
/>
</label>
<label>
City:
<input
value={person.artwork.city}
onChange={handleCityChange}
/>
</label>
<label>
Image:
<input
value={person.artwork.image}
onChange={handleImageChange}
/>
</label>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img
src={person.artwork.image}
alt={person.artwork.title}
/>
</>
);
}
ivi:
import { component, useState } from 'ivi';
import { htm } from '@ivi/htm';
const Form = component((c) => {
const [person, setPerson] = useState(c, {
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
setPerson({
...person(),
name: e.target.value
});
}
function handleTitleChange(e) {
const p = person();
setPerson({
...p,
artwork: {
...p.artwork,
title: e.target.value
}
});
}
function handleCityChange(e) {
const p = person();
setPerson({
...p,
artwork: {
...p.artwork,
city: e.target.value
}
});
}
function handleImageChange(e) {
const p = person();
setPerson({
...p,
artwork: {
...p.artwork,
image: e.target.value
}
});
}
return () => {
const p = person();
return htm`
<label>
Name:
<input
*value=${p.name}
@input=${handleNameChange}
/>
</label>
<label>
Title:
<input
*value=${p.artwork.title}
@input=${handleTitleChange}
/>
</label>
<label>
City:
<input
*value=${p.artwork.city}
@input=${handleCityChange}
/>
</label>
<label>
Image:
<input
*value=${p.artwork.image}
@input=${handleImageChange}
/>
</label>
<p>
<i>${p.artwork.title}</i>
\v by ${p.name}
<br>
(located in ${p.artwork.city} )
</p>
<img
src=${p.artwork.image}
alt=${p.artwork.title}
/>
`;
};
});
export default Form;
React:
import { useState } from 'react';
export default function Form() {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
if (status === 'success') {
return <h1>That's right!</h1>
}
async function handleSubmit(e) {
e.preventDefault();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return (
<>
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={handleTextareaChange}
disabled={status === 'submitting'}
/>
<br />
<button disabled={
answer.length === 0 ||
status === 'submitting'
}>
Submit
</button>
{error !== null &&
<p className="Error">
{error.message}
</p>
}
</form>
</>
);
}
function submitForm(answer) {
// Pretend it's hitting the network.
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if (shouldError) {
reject(new Error('Good guess but a wrong answer. Try again!'));
} else {
resolve();
}
}, 1500);
});
}
ivi:
import { component, useState } from 'ivi';
import { htm } from '@ivi/htm';
const Form = component((c) => {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
async function handleSubmit(e) {
e.preventDefault();
const answer = answer();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return () => {
if (status() === 'success') {
return htm`h1 'That's right!`;
}
return htm`
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form @submit=${handleSubmit}>
<textarea
*value=${answer()}
@input=${handleTextareaChange}
disabled=${status() === 'submitting'}
/>
<br>
<button
disabled=${
answer().length === 0 ||
status() === 'submitting'
}
Submit
/>
${error() && htm`<p class="Error">${error().message}</p>`}
</form>
`;
};
});
function submitForm(answer) {
// Pretend it's hitting the network.
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if (shouldError) {
reject(new Error('Good guess but a wrong answer. Try again!'));
} else {
resolve();
}
}, 1500);
});
}
React:
import { useState } from 'react';
export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
return (
<>
<h2>Let’s check you in</h2>
<label>
First name:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
Your ticket will be issued to: <b>{fullName}</b>
</p>
</>
);
}
ivi:
import { component, useState } from 'ivi';
import { htm } from '@ivi/htm';
const Form = component((c) => {
const [firstName, setFirstName] = useState(c, '');
const [lastName, setLastName] = useState(c, '');
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
return () => {
const fullName = firstName + ' ' + lastName;
return htm`
<h2>Let's check you in</h2>
<label>
First Name: \v
<input *value=${firstName()} @input=${handleFirstNameChange} />
</label>
<label>
Last name: \v
<input *value=${lastName()} @input=${handleLastNameChange} />
<p>
Your ticket will be issued to: \v
<b>${fullName}</b>
</p>
`;
};
});
React:
import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';
export default function Messenger() {
const [to, setTo] = useState(contacts[0]);
return (
<div>
<ContactList
contacts={contacts}
selectedContact={to}
onSelect={contact => setTo(contact)}
/>
<Chat key={to.email} contact={to} />
</div>
)
}
const contacts = [
{ name: 'Taylor', email: 'taylor@mail.com' },
{ name: 'Alice', email: 'alice@mail.com' },
{ name: 'Bob', email: 'bob@mail.com' }
];
ivi:
import { component, useState } from 'ivi';
import { htm } from '@ivi/htm';
import { Identity } from '@ivi/identity';
import Chat from './Chat.js';
import ContactList from './ContactList.js';
const Messenger = component((c) => {
const [_to, setTo] = useState(contacts[0]);
const onSelect = (contact) => { setTo(contact); };
return () => {
const to = _to();
return htm`
<div>
${ContactList({
contacts,
selectedContact: to,
onSelect,
})}
${Identity({ key: to.email, contact: to })}
</div>
`;
}
});
export default Messenger;
const contacts = [
{ name: 'Taylor', email: 'taylor@mail.com' },
{ name: 'Alice', email: 'alice@mail.com' },
{ name: 'Bob', email: 'bob@mail.com' }
];
React:
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
return (
<>
<h1>Prague itinerary</h1>
<AddTask
onAddTask={handleAddTask}
/>
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: 'Visit Kafka Museum', done: true },
{ id: 1, text: 'Watch a puppet show', done: false },
{ id: 2, text: 'Lennon Wall pic', done: false }
];
ivi:
import { component, useReducer } from 'ivi';
import { htm } from '@ivi/htm';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
const TaskApp = component((c) => {
const [tasks, dispatch] = useReducer(c, initialTasks, tasksReducer);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
return () => htm`
<h1>Prague itinerary</h1>
${AddTask({ onAddTask })}
${TaskList({
tasks,
onChangeTask: handleChangeTask,
onDeleteTask: handleDeleteTask,
})}
`;
});
export default TaskApp;
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: 'Visit Kafka Museum', done: true },
{ id: 1, text: 'Watch a puppet show', done: false },
{ id: 2, text: 'Lennon Wall pic', done: false }
];
React:
import { createContext, useContext } from 'react';
const LevelContext = createContext(0);
function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 0:
throw Error('Heading must be inside a Section!');
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('Unknown level: ' + level);
}
}
function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
function Page() {
return (
<Section>
<Heading>Title</Heading>
<Section>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Section>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Section>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
ivi:
import { component, context } from 'ivi';
import { htm } from '@ivi/htm';
const [getLevelContext, LevelContext] = context();
const Heading = (c) => {
const level = getLevelContext(c);
return (children) => {
switch (level) {
case 0:
throw Error('Heading must be inside a Section!');
case 1:
return htm`<h1>${children}</h1>`;
case 2:
return htm`<h2>${children}</h2>`;
case 3:
return htm`<h3>${children}</h3>`;
case 4:
return htm`<h4>${children}</h4>`;
case 5:
return htm`<h5>${children}</h5>`;
case 6:
return htm`<h6>${children}</h6>`;
default:
throw Error('Unknown level: ' + level);
}
}
}
const Section = component(c) => {
const level = useContext(LevelContext);
return (children) => htm`
<section class="section">
${LevelContext(level + 1, children)}
</section>
`;
}
const Page = () => (
Section([
Heading("Title"),
Section([
Heading("Heading"),
Heading("Heading"),
Heading("Heading"),
]),
Section([
Heading("Sub-heading"),
Heading("Sub-heading"),
Heading("Sub-heading"),
Section([
Heading("Sub-sub-heading"),
Heading("Sub-sub-heading"),
Heading("Sub-sub-heading"),
]),
]),
])
);
React:
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
ivi:
import { component } from 'ivi';
import { htm } from '@ivi/htm';
const Counter = component((c) => {
let _ref = 0;
function handleClick() {
_ref += 1;
alert('You clicked ' + _ref + ' times!');
}
return htm`<button @click=${handleClick}>Click me!</button>`;
});
export default Counter;
React:
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
ivi:
import { component } from 'ivi';
import { htm } from '@ivi/htm';
const Form = component((c) => {
let _inputRef = null;
const onInputRef = (e) => _inputRef = e;
function handleClick() {
inputRef.current.focus();
}
return () => htm`
<input ${onInputRef}/>
<button @click=${handleClick}>Focus the input</button>
`;
});
React:
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}
ivi:
import { component, useEffect } from 'ivi';
import { htm } from '@ivi/htm';
const VideoPlayer = component((c) => {
let _ref = null;
const getRef = (e) => _ref = e;
const update = useEffect(c, (isPlaying) => {
if (isPlaying) {
_ref.play();
} else {
_ref.pause();
}
}, strictEq);
return ({ src, isPlaying }) => (
update(isPlaying),
htm`<video ${getRef} src=${src} loop playsInline />`);
});
const App = component((c) => {
const [isPlaying, setIsPlaying] = useState(c, false);
return htm`
<button @click=${onClick}>${isPlaying() ? 'Pause' : 'Play'}</button>
${VideoPlayer({
isPlaying: isPlaying(),
src: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
})}
`;
});
export default App;
React:
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return (
<>
<h1>Welcome to the {roomId} room!</h1>
<input value={message} onChange={e => setMessage(e.target.value)} />
</>
);
}
export default function App() {
const [roomId, setRoomId] = useState('general');
return (
<>
<label>
Choose the chat room:{' '}
<select
value={roomId}
onChange={e => setRoomId(e.target.value)}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
</label>
<hr />
<ChatRoom roomId={roomId} />
</>
);
}
ivi:
import { component, useState, useEffect } from 'ivi';
import { htm } from '@ivi/htm';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
const ChatRoom = component((c) => {
const [message, setMessage] = useState(c, '');
const updateRoomConnection = useEffect(c, (roomId) => {
const options = {
serverUrl,
roomId,
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, strictEq);
const onInput = (ev) => { setMessage(ev.target.value); };
return ({ roomId }) => (
updateRoomConnection(roomId),
htm`
<h1>Welcome to the ${roomId} room!</h1>
<input *value=${message()} @input=${onInput} />
`,
);
});
const App = component((c) => {
const [roomId, setRoomId] = useState('general');
const onChange = (ev) => setRoomId(ev.target.value);
return htm`
<label>
Choose the chat room: \v
<select
*value=${roomId}
@change=${onChange}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
<hr>
${ChatRoom({ roomId: roomId() })}
`;
});
export default App;
React:
import { useState, useEffect } from 'react';
function useDelayedValue(value, delay) {
const [delayedValue, setDelayedValue] = useState(value);
useEffect(() => {
setTimeout(() => {
setDelayedValue(value);
}, delay);
}, [value, delay]);
return delayedValue;
}
function usePointerPosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMove(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
window.addEventListener('pointermove', handleMove);
return () => window.removeEventListener('pointermove', handleMove);
}, []);
return position;
}
export default function Canvas() {
const pos1 = usePointerPosition();
const pos2 = useDelayedValue(pos1, 100);
const pos3 = useDelayedValue(pos2, 200);
const pos4 = useDelayedValue(pos3, 100);
const pos5 = useDelayedValue(pos3, 50);
return (
<>
<Dot position={pos1} opacity={1} />
<Dot position={pos2} opacity={0.8} />
<Dot position={pos3} opacity={0.6} />
<Dot position={pos4} opacity={0.4} />
<Dot position={pos5} opacity={0.2} />
</>
);
}
function Dot({ position, opacity }) {
const style = {
position: 'absolute',
backgroundColor: 'pink',
borderRadius: '50%',
opacity,
transform: `translate(${position.x}px, ${position.y}px)`,
pointerEvents: 'none',
left: -20,
top: -20,
width: 40,
height: 40,
};
return (
<div style={style} />
);
}
ivi:
import {
createRoot, update, component, useState, useEffect, shallowEqArray,
} from "ivi";
import { htm } from "@ivi/htm";
const ZERO: Point = { x: 0, y: 0 };
function useDelayedValue(c) {
const [delayedValue, setDelayedValue] = useState(c, ZERO);
return [
delayedValue,
useEffect(c, ([value, delay]) => {
setTimeout(() => {
setDelayedValue(value);
}, delay);
}, shallowEqArray)
];
}
function usePointerPosition(c) {
const [position, setPosition] = useState(c, ZERO);
useEffect(c, () => {
const onMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('pointermove', onMove);
return () => window.removeEventListener('pointermove', onMove);
})();
return position;
}
const Canvas = component((c) => {
const _pos1 = usePointerPosition(c);
const [_pos2, updatePos2] = useDelayedValue(c);
const [_pos3, updatePos3] = useDelayedValue(c);
const [_pos4, updatePos4] = useDelayedValue(c);
const [_pos5, updatePos5] = useDelayedValue(c);
return () => {
const pos1 = _pos1();
const pos2 = _pos2();
const pos3 = _pos3();
const pos4 = _pos4();
const pos5 = _pos5();
updatePos2([pos1, 100]);
updatePos3([pos2, 200]);
updatePos4([pos3, 100]);
updatePos5([pos3, 50]);
return [
Dot({ position: pos1, opacity: 1 }),
Dot({ position: pos2, opacity: 0.8 }),
Dot({ position: pos3, opacity: 0.6 }),
Dot({ position: pos4, opacity: 0.4 }),
Dot({ position: pos5, opacity: 0.2 }),
];
};
});
const Dot = ({ position, opacity }) => htm`
<div
~position="absolute"
~background-color="pink"
~border-radius="50%"
~pointer-events="none"
~left="-20px"
~top="-20px"
~width="40px"
~height="40px"
~opacity=${opacity}
~transform=${`translate(${position.x}px,${position.y}px)`}
/>
`;
update(
createRoot(document.getElementById("app")!),
Canvas(),
);