Created
February 7, 2024 01:54
-
-
Save piq9117/0e6607b428f7f5e0e41e747a24a307c1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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