Skip to content

Instantly share code, notes, and snippets.

@huksley
Last active July 6, 2021 07:21
Show Gist options
  • Save huksley/1b3b8d2dc7def2dee55da6ae463f6a8f to your computer and use it in GitHub Desktop.
Save huksley/1b3b8d2dc7def2dee55da6ae463f6a8f to your computer and use it in GitHub Desktop.
Fully typed ReactJS CRUD example
import { useState } from "react";
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
export type Unsaved<T extends { _id: string; updatedAt: Date }> = Optional<
T,
"_id" | "updatedAt"
>;
interface User {
_id: string;
name: string;
updatedAt: Date;
}
/** Adds new user to database */
const saveUser = (user: Unsaved<User>, users: User[]): Promise<User> => {
if (users.find(u => u.name === user.name)) {
return Promise.reject(new Error("Duplicate name: " + user.name));
}
return Promise.resolve({
...user,
_id: "id" + Date.now(),
updatedAt: new Date()
});
};
/** Updates user in the database */
const updateUser = (user: User, users: User[]): Promise<User> => {
if (users.find(u => u.name === user.name && u._id != user._id)) {
return Promise.reject(new Error("Duplicate name: " + user.name));
}
return Promise.resolve(user);
};
const isSaved = (user: User | Unsaved<User>): user is User => {
return user._id != undefined;
};
const Form = ({
user: initialUser,
onSave,
onCancel
}: {
user: User | Unsaved<User>;
onSave?: (
event: React.MouseEvent<HTMLInputElement, Event>,
user: User | Unsaved<User>
) => void;
onCancel?: (event: React.MouseEvent<HTMLInputElement, Event>) => void;
}) => {
const [user, setUser] = useState<Unsaved<User>>(initialUser);
return (
<div>
{user._id} user name:{" "}
<input
value={user.name}
onChange={event => {
const name = event.target.value;
setUser(user => ({
...user,
name
}));
}}
/>
<input type="Submit" value="Save" onClick={event => onSave && onSave(event, user)} />
<input type="Submit" value="Cancel" onClick={event => onCancel && onCancel(event)} />
</div>
);
};
const Page = () => {
const [users, setUsers] = useState<User[]>([]);
const [edit, setEdit] = useState<Unsaved<User> | undefined>();
const [error, setError] = useState<Error | undefined>(undefined);
return (
<div>
<h2>Users</h2>
{error && <p>Error: {error.message}</p>}
{edit ? (
<Form
user={edit}
onSave={(_event, user) => {
if (isSaved(user)) {
updateUser(user, users)
.then(updatedUser => {
setUsers(users => [
...users.filter(u => u._id != updatedUser._id),
updatedUser
]);
setEdit(undefined);
setError(undefined);
})
.catch(error => {
// FIXME: type assumption
setError(error);
});
} else {
saveUser(user, users)
.then(newUser => {
setUsers(users => [...users, newUser]);
setEdit(undefined);
setError(undefined);
})
.catch(error => {
// FIXME: type assumption
setError(error);
});
}
}}
onCancel={() => {
setEdit(undefined);
setError(undefined);
}}
/>
) : (
<>
{users.length === 0 ? (
<p>
<i>No users found</i>
</p>
) : (
<ul>
{users.map((user, index) => (
<li key={index}>
User {user.name}{" "}
<a
href="#"
onClick={event => {
event.preventDefault();
setEdit(user);
}}
>
Edit
</a>
</li>
))}
</ul>
)}
<a
href="#"
onClick={event => {
event.preventDefault();
setEdit({ name: "New user" });
}}
>
Add new
</a>
</>
)}
</div>
);
};
export default Page;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment