Issue
I am learning FP and I am trying to figure out how to handle events in react. For example, let's use following scenario:
interface Todo {
task: string
done: boolean
}
interface TodoProps {
todo: Todo
onChange: ChangeEventHandler<HTMLInputElement>
}
function TodoItem({todo, onChange}: TodoProps) {
return (
<label>
{todo.task}
<input type="checkbox" checked={todo.done} onChange={onChange}/>
</label>
)
}
function App() {
const [todo, setTodo] = useState({
task: "Some task",
done: false
});
const toggleTodo = // I need to implement this
return (
<main>
<TodoItem onChange={toggleTodo} todo={todo}/>
</main>
)
}
Nothing fancy, just basic todo app.
In the missing function I need to create object with updated done
property. To do that I create Ramda's lens focused on done
property.
const doneLens = lensProp('done');
Then it should be easy to finish the goal. All I need is to compose setTodo
with Rambda's over
.
const toggleDone = () => compose(setTodo, over(doneLens, toggleBoolean))(todo)
But here is the issue. I am getting this ts error:
TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type '<T>(value: T) => T' is not assignable to parameter of type '(x0: unknown, x1: unknown, x2: unknown) => SetStateAction<{ task: string; done: boolean; }>'.
Type 'unknown' is not assignable to type 'SetStateAction<{ task: string; done: boolean; }>'.
Type 'unknown' is not assignable to type '(prevState: { task: string; done: boolean; }) => { task: string; done: boolean; }'.
In pure js this function should work, but ts can't infer return type of over
function. It's logical. The over
is generic so let's try to add explicit type.
const toggleDone = () => compose(setTodo, over<Todo>(doneLens, toggleBoolean))(todo)
And I get:
TS2554: Expected 3 arguments, but got 2.
index.d.ts(669, 51): An argument for 'value' was not provided.
TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'Todo' is not assignable to parameter of type '(x0: unknown, x1: unknown, x2: unknown) => SetStateAction<{ task: string; done: boolean; }>'.
Type 'Todo' provides no match for the signature '(x0: unknown, x1: unknown, x2: unknown): SetStateAction<{ task: string; done: boolean; }>'.
Ramda functions are curried by default, but if I can read the error correctly it seem that when I add explicit type then the currying does not working.
I can come up with workaround:
const overDone: (t: Todo) => Todo = over(doneLens, toggleBoolean);
const toggleDone = () => compose(setTodo, overDone)(todo);
This works because return type of overDone
match with input type of setTodo
.
But, my question is. How to fix the oneliner? Or if you know better way to handle similar scenarios with lenses, function composition, useState hook and typescript I am curious to see so.
Solution
Add the type Todo
to the lensProp
call. You can also move it out of the component, because you don't need to generate the function whenever the component re-renders:
const doneLens = lensProp<Todo>("done");
const toggleDone = over(doneLens, not);
Since setTodo
is a function that can call another function (see https://reactjs.org/docs/hooks-reference.html#functional-updates) to which it passes the current todo
, you don't need R.compose
:
const toggleTodo = () => setTodo(over(doneLens, not));
And this how your component would look (sandbox):
const doneLens = lensProp<Todo>("done");
const toggleDone = over(doneLens, not);
function App() {
const [todo, setTodo] = useState({
task: "Some task",
done: false
});
const toggleTodo = () => setTodo(toggleDone);
return (
<main>
<TodoItem onChange={toggleTodo} todo={todo} />
</main>
);
}
A simpler option would be to use R.evolve
(sandbox):
const toggleDone = evolve({ done: not });
function App() {
const [todo, setTodo] = useState({
task: "Some task",
done: false
});
const toggleTodo = () => setTodo(toggleDone);
console.log(todo);
return (
<main>
<TodoItem onChange={toggleTodo} todo={todo} />
</main>
);
}
Answered By - Ori Drori
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.