The createWyre
function is designed for synchronizing data between clients in real-time.
import { createWyre } from "@wyre-client/core";
Initialize the hook with some initial data. The data can be any javascript object or array.
const sync = createWyre({
data: {
users: []
},
onChange: () => void
});
This method does two things:
- initializes the data synchronization for a specific item. It takes a string
id
as an argument, which uniquely identifies the item that needs to be synced. - syncs and therefore replicates the data object to another user using the same
uniqueId
for theirsync.init
const data = await sync.init(uniqueId);
Returns a promise that resolves to the synchronized data object.
The usePresence
hook is a custom hook designed to manage user presence information in real-time. This hook helps track user-related information like mouse position and state, user name, and user color.
import { usePresence } from "@wyre-client/core";
Initialize the hook:
const presence = usePresence();
This method initializes the user's presence information. It takes two string arguments: roomName
, which uniquely identifies the presence room, and userName
, which represents the user's name.
const presenceDetails = await presence.init({
presenceId: "presenceRoom",
});
Returns a promise that resolves to an object containing the presence information for all connected users.
This method adds the user's presence information to the room. It takes an object IUserDetails
as an argument, which contains the user's details, such as their name.
presence.add({
name,
});
The presenceDetails
variable returned from the presence.init()
call is an object that contains presence information for all connected users. This information can be used to display user-specific UI elements, such as avatars and cursors, to visualize the real-time presence and activities of the users in the application.
The IPresenceDetails
interface has the following structure:
{
users: {
[userId: string]: {
mousePosition: number[];
mouseState: string;
name: string;
userColor: string;
}
}
}
To display user avatars, iterate through the keys of the presenceDetails.users
object and render the Avatar
component for each user. The following code snippet demonstrates how to render user avatars using the antd
library:
<Avatar.Group>
{presenceDetails?.users.keys().map((userId: string) => {
return (
<Avatar
style={{
backgroundColor: presenceDetails.users[userId].userColor,
}}
>
{getInitials(presenceDetails.users[userId].name)}
</Avatar>
);
})}
</Avatar.Group>
To display custom cursors for each user, iterate through the keys of the presenceDetails.users
object and render the Cursor
component for each user. This helps visualize the real-time mouse position and state of the users in the application. The following code snippet demonstrates how to render user cursors:
{presenceDetails?.users.keys().map((userId: string) => {
return (
<div
style={{
top: presenceDetails.users[userId].mousePosition[0],
left: presenceDetails.users[userId].mousePosition[1],
}}
className="cursor"
>
<Cursor color={presenceDetails.users[userId].userColor} />
<div>{presenceDetails.users[userId].mouseState}</div>
</div>
);
})}
The useSync
hook is a custom hook designed for synchronizing data between clients in real-time. This hook provides an easy way to keep application data in sync among different users using the same application instance.
import { useSync } from "@wyre-client/core";
Initialize the hook with some initial data. The data can be any javascript object or array.
const sync = useSync({
data: {
users: []
},
});
This method does two things:
- initializes the data synchronization for a specific item. It takes a string
id
as an argument, which uniquely identifies the item that needs to be synced. - syncs and therefore replicates the data object to another user using the same
uniqueId
for theirsync.init
const data = await sync.init(uniqueId);
Returns a promise that resolves to the synchronized data object.
Arrays (root level or nested) returned from the useSync
init()
call can be treated as a normal javascript array for modifications. Addition and deletion of items has to be done through the following methods.
The following array methods are currently available
push a new value to the array
arr.push(newValue);
This will add a new key newKey
to the object with the value "newValue"
.
delete value from an index
:
arr.delete(5);
This will remove the key
from the object and mark it as deleted, making it unavailable for further access.
get the index of an element in the array:
arr.indexOf(10);
map through values in the array:
arr.map(val => {...})
get the length of the array
arr.length
Object returned from the useSync
init()
call can be treated as a normal javascript object for modifications. Addition and deletion of keys has to be done through the following methods.
Keys added and removed using delete
keyword or through object assignment obj[newKey] = newValue
will not track changes to newKey
.
To add a new key to the data, you can use the insert
method:
obs.insert("newKey", newValue);
This will add a new key newKey
to the object with the value "newValue"
.
To remove a key from the data, you can use the delete
method:
obs.delete("key");
This will remove the key
from the object and mark it as deleted, making it unavailable for further access.
To modify the data, you can use assignment operations as usual:
obs.counter = obs.counter + 1;
The Object.keys
, Object.values
and Object.entries
methods will not work as expected with objects returned by sync.init
. Please use the following to iterate over keys:
Iterate over keys in the root level or nested object. Example
{presenceDetails?.users.keys().map((userId: string) => {
return (
<div
style={{
top: presenceDetails.users[userId].mousePosition[0],
left: presenceDetails.users[userId].mousePosition[1],
}}
className="cursor"
>
<Cursor color={presenceDetails.users[userId].userColor} />
<div>{presenceDetails.users[userId].mouseState}</div>
</div>
);
})}
import { Avatar, Button, Col, Input, Row, Space } from "antd";
import Modal from "antd/es/modal/Modal";
import React, { useState } from "react";
import { Cursor } from "../components/Cursor/Cursor";
import { useSync, usePresence } from "@wyre-client/core";
import { getInitials } from "../utils/get-initials";
import "./Todo.css";
type Todo = {
text: string;
done: boolean;
};
type SyncData = {
todos: Todo[];
};
const initialData: SyncData = { todos: [] };
const TICKED_STYLE = {
background: "#99d98c50",
color: "#52b69a",
fontWeight: "bolder",
};
export const Todo: React.FC = () => {
/**
* have we loaded everything?
*/
const [loaded, setLoaded] = useState(true);
/**
* real time collaborative data
*/
const [data, setData] = useState<SyncData>(initialData);
/**
* users presence details
*/
const [presenceDetails, setPresenceDetails] = useState<any>(null);
/**
* text for a new todo
*/
const [newTodoText, setNewTodoText] = useState("");
/**
* Has the user entered details
*/
const [detailsEntered, setDetailsEntered] = useState(false);
/**
* The id of this todo list
*/
const [todoId, setTodoId] = useState("");
/**
* Your name
*/
const [name, setName] = useState("");
/**
* This is where the magic happens
*/
const sync = useSync({
data: initialData,
});
const presence = usePresence();
const load = async () => {
setLoaded(false);
const data = await sync.init(todoId);
const presenceDetails = await presence.init({
presenceId: "todopresence101",
});
presence.add({
name,
});
setPresenceDetails(presenceDetails);
setData(data);
setLoaded(true);
};
const addNewTodo = () => {
data.todos.push({
text: newTodoText,
done: false,
});
setNewTodoText("");
};
const toggleTodo = (todo: Todo) => {
todo.done = !todo.done;
};
const handleTodoTextChange = (todo: Todo, value: string) => {
todo.text = value;
};
const handleTodoDelete = (todo: Todo) => {
const index = data.todos.indexOf((_todo: Todo) => _todo.text === todo.text);
if (index !== -1) {
data.todos.delete(index);
}
};
const handleNewTodoTextChange = (value: string) => {
setNewTodoText(value);
};
const handleTodoCreate = async () => {
await load();
setDetailsEntered(true);
};
if (!loaded) {
return <div>Loading...</div>;
}
if (!detailsEntered) {
return (
<div className="container">
<Modal
open={true}
okButtonProps={{ disabled: true }}
cancelButtonProps={{ disabled: true }}
>
<div>
<p>Enter Your Name</p>
<p>
<Input
onChange={(e) => {
setName(e.target.value);
}}
/>
</p>
</div>
<div>
<p>Enter Todo ID</p>
<p>
<Input
onChange={(e) => {
setTodoId(e.target.value);
}}
/>
</p>
<p>
<Button onClick={handleTodoCreate}>Enter</Button>
</p>
</div>
</Modal>
</div>
);
}
return (
<div className="container">
<div className="avatars">
<Avatar.Group>
{presenceDetails?.users.keys().map((userId: string) => {
return (
<Avatar
style={{
backgroundColor: presenceDetails.users[userId].userColor,
}}
>
{getInitials(presenceDetails.users[userId].name)}
</Avatar>
);
})}
</Avatar.Group>
</div>
{presenceDetails?.users.keys().map((userId: string) => {
return (
<div
style={{
top: presenceDetails.users[userId].mousePosition[0],
left: presenceDetails.users[userId].mousePosition[1],
}}
className="cursor"
>
<Cursor color={presenceDetails.users[userId].userColor} />
<div>{presenceDetails.users[userId].mouseState}</div>
</div>
);
})}
<h1>My Todos</h1>
<p>Todo ID / {todoId}</p>
<div className="actions">
<div>
<Space direction="horizontal">
<Input
placeholder="New Todo"
onChange={(e) => handleNewTodoTextChange(e.target.value)}
value={newTodoText}
/>
<Button style={{ width: 120 }} onClick={addNewTodo}>
Add Todo
</Button>
</Space>
</div>
</div>
<div className="todo-list-container">
{data.todos.length === 0 && (
<h5>Nothing to do today. You've got a day to yourself :)</h5>
)}
{data.todos.map((todo: Todo, index: number) => (
<Row className="todo-item">
<Col span={17}>
<Input
value={todo.text}
placeholder="Enter Todo"
onChange={(event) => {
handleTodoTextChange(todo, event.target.value);
}}
/>
</Col>
<Col span={2}>
<Button
style={todo.done ? TICKED_STYLE : {}}
shape="circle"
ghost
type="text"
onClick={() => toggleTodo(todo)}
>
β
</Button>
</Col>
<Col span={3}>
<Button
onClick={() => {
handleTodoDelete(todo);
}}
shape="circle"
danger
type="text"
>
π
</Button>
</Col>
</Row>
))}
</div>
</div>
);
};