@scull7 This is not an problem originating in ReasonReact but rather with the way ReactJS is being used.
The global state in TodoApp.re
is being mutated, so even if you return something from the reducer, it is actually the same reference. React won’t re-render children when this happens, as mentioned in the docs: https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-dispatch
About how to fix this in Reason, I’d prob get rid of the bs.deriving stuff in the global state type, and use spread operator in the updater add
, remove
and complete
.
This is a version of the file that worked for me locally (haven’t tested it much but it compiles and the UI updates when adding an item):
TodoApp.re (click to expand)
module TodoId = {
type t = string;
let prefix = ref(0);
let make = () => {
let prefix = Js.Math.random_int(1000000, 9999999)->string_of_int;
let suffix = Js.Date.now()->int_of_float->string_of_int;
{j|$prefix::$suffix|j};
};
};
module Todo = {
[@bs.deriving abstract]
type t = {
id: TodoId.t,
mutable finished: bool,
text: string,
};
let make = (~finished=false, text) =>
t(~id=TodoId.make(), ~finished, ~text);
let complete = t => {
t->finishedSet(true);
t;
};
let isSame = (t1, t2) => t1->idGet === t2->idGet;
};
module TodoList = {
type t = array(Todo.t);
let contains = (list, todo) => list->Belt.Array.some(Todo.isSame(todo));
let complete = (list, target) =>
list->Belt.Array.map(todo =>
todo->Todo.isSame(target) ? todo->Todo.complete : todo
);
let add = (list, todo) =>
if (!list->contains(todo)) {
[|todo|]->Belt.Array.concat(list);
} else {
// panic - should be unreachable
let id = todo->Todo.idGet;
Js.Exn.raiseError({j|Could not add todo $id, it already exists.|j});
};
let remove = (list, todo) =>
list->Belt.Array.keep(current => !todo->Todo.isSame(current));
};
type t = {
today: TodoList.t,
tomorrow: TodoList.t,
};
type day =
| Today
| Tomorrow;
type action =
| Add(day, Todo.t)
| Complete(day, Todo.t)
| Remove(day, Todo.t);
let appState = {today: [||], tomorrow: [||]};
let add = (state, day, todo) => {
switch (day) {
| Today => {...state, today: state.today->TodoList.add(todo)}
| Tomorrow => {...state, tomorrow: state.tomorrow->TodoList.add(todo)}
};
};
let complete = (state, day, todo) => {
switch (day) {
| Today => {...state, today: state.today->TodoList.complete(todo)}
| Tomorrow => {...state, tomorrow: state.tomorrow->TodoList.complete(todo)}
};
};
let remove = (state, day, todo) => {
switch (day) {
| Today => {...state, today: state.today->TodoList.remove(todo)}
| Tomorrow => {...state, tomorrow: state.tomorrow->TodoList.remove(todo)}
};
};
let getDay = (state, day) => {
switch (day) {
| Today => state.today
| Tomorrow => state.tomorrow
};
};
let reducer = (state, action) => {
switch (action) {
| Add(day, todo) => add(state, day, todo)
| Complete(day, todo) => complete(state, day, todo)
| Remove(day, todo) => remove(state, day, todo)
};
};
let useTodoReducer = () => React.useReducer(reducer, appState);