Skip to content

Instantly share code, notes, and snippets.

@piq9117
Created February 7, 2024 01:54
Show Gist options
  • Save piq9117/0e6607b428f7f5e0e41e747a24a307c1 to your computer and use it in GitHub Desktop.
Save piq9117/0e6607b428f7f5e0e41e747a24a307c1 to your computer and use it in GitHub Desktop.
import React, { useState, useReducer } from "react";
import "./App.css";
type ActionType =
| { type: "ADD"; text: string }
| { type: "REMOVE"; id: number };
abstract class Functor<A> {
public abstract map<B>(fn: (a: A) => B): Functor<B>;
}
class Some<A> extends Functor<A> {
private value: A;
constructor(value: A) {
super();
this.value = value;
}
map<B>(fn: (a: A) => B): Some<B> {
return new Some(fn(this.value));
}
getOrElseValue<B>(defaultValue: B): A | B {
if (this.value !== undefined) {
return this.value;
} else {
return defaultValue;
}
}
}
class None<A> extends Functor<A> {
constructor() {
super();
}
map<B>(_fn: (a: A) => B): None<B> {
return new None();
}
getOrElseValue<B>(defaultValue: B): B {
return defaultValue
}
}
type Option<A> = Some<A> | None<A>
function fromNullable<A>(a?: A): Option<A> {
if (a === undefined || a === null) {
return new None();
} else {
return new Some(a);
}
}
interface ActionHandlers {
[type: string]: (state: string[], action: ActionType) => string[];
}
const isAddAction = (action: ActionType): action is {type: "ADD", text: string;} => {
return action.type === "ADD";
}
const isRemoveAction = (action: ActionType): action is {type: "REMOVE", id: number;} => {
return action.type === "REMOVE";
}
const useTodosReducer = (initialTodo: string[]) => {
const ADD = (state: string[], action: ActionType): string[] => {
if(isAddAction(action)) {
return state.concat(action.text);
} else {
return state
}
}
const REMOVE = (state: string[], action: ActionType): string[] => {
if (isRemoveAction(action)) {
return state.filter((_, todoIdx) => todoIdx !== action.id);
} else {
return state
}
};
const handlers: ActionHandlers = {
ADD,
REMOVE,
};
const [todos, dispatch] = useReducer(
(state: string[], action: ActionType) =>
fromNullable(handlers[action.type])
.map(fn => fn(state, action))
.getOrElseValue([]),
initialTodo
);
const addTodo = (todoItem: string) =>
dispatch({ type: "ADD", text: todoItem });
const removeTodo = (todoItemId: number) =>
dispatch({ type: "REMOVE", id: todoItemId });
return { todos, addTodo, removeTodo };
};
function App() {
const [todoItem, setTodoItem] = useState("");
const { todos, addTodo, removeTodo } = useTodosReducer([]);
const todoInputHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setTodoItem(value);
};
const addTodoHandler = () => {
if (todoItem.trim() === "") {
return;
}
addTodo(todoItem);
setTodoItem("");
};
return (
<div className="app-container">
<ul>
{todos.map((todo, idx) => (
<li key={idx}>
<span>{todo}</span>
<button onClick={() => removeTodo(idx)}>X</button>
</li>
))}
</ul>
<div>
<input value={todoItem} onChange={(e) => todoInputHandler(e)} />
<button onClick={() => addTodoHandler()}>add todo</button>
</div>
</div>
);
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment