Been searching for answers to this and getting a lot of people asking how to make it trigger once. My issue is that it’s only triggering once when it should be triggering multiple times.
I have a CodeSandbox set up with my code, and it has the issue I’m having.
Really basic todo app, this is just the list aspect. handle_text_change()
is passed to the items and when changes to the name or completed status are made it’s passed back and it updates immediately.
I’m trying to use RxJS debounce to collect together changes and then make them all at once, currently all handle_submit_changes()
does is just log that it has been called and updates the known_todo_list which acts as the difference for the next submit and fallback if the submit fails.
export const TodoList: React.FC<TodoListProps> = ({ todos }) => {
const [todo_list, set_todo_list] = useState<Todo[]>(todos);
const [_known_todo_list, set_known_todo_list] = useState<Todo[]>(todos);
const todo_list_subject = new Subject<Todo[]>();
const handle_text_change = (id: number, text: string, completed: boolean) => {
console.log("handle_text_change", { id, text, completed });
set_todo_list((prev_todo_list) => {
const updated_todo_list = prev_todo_list.map((todo) =>
todo.id === id ? { ...todo, text, completed } : todo
);
console.log(`next`);
todo_list_subject.next(updated_todo_list);
return updated_todo_list;
});
};
const handle_submit_changes = () => {
console.log("handle_submit_changes", todo_list);
set_known_todo_list(todo_list);
};
const handle_todo_list_observer: Observer<Todo[]> = {
next: handle_submit_changes,
error: (err: any) => console.error(err),
complete: () => console.log("todo_list_observer complete")
};
useEffect(() => {
console.log("subscription");
const subscription = todo_list_subject
.pipe(debounceTime(1000))
.subscribe(handle_todo_list_observer);
return () => {
console.log("unsubscribe");
subscription.unsubscribe();
};
}, []); //Removing this dependency array makes it subscribed and unsubscribe on every change making it not work
return (
<ul>
{todo_list.map((todo) => (
<TodoItem key={todo.id} todo={todo} onTodoChange={handle_text_change} />
))}
</ul>
);
};
This is the output that I see after clicking the middle checkbox a few times, the teal lines signify that I have let time pass, enough that a debounce should have happened but didn’t.
I have no idea why it’s only running once and why react_devtools_backend.js jumps in to log "next" too but only the second time it fails onwards
2
Answers
I think this is the expected behaviour for
debounceTime
, it will only emit the last value and not any of the intermediate values after the debounce expires.If I’ve understood correctly you are expecting to see an emission for each click but to only receive them after the 1 second debounce has passed. I think you would need to use
buffer
instead.Something like:
This will give you all of the emissions but delayed until the debounce expires. Buffer returns all of the emissions as an array so adding
switchMap(from)
will convert these into individual emissions.TodoList() is being run on each click / checkbox toggle, which I assume is unintentional. The issue doesn’t appear to have anything to do with debounceTime (if you put a tap in before debounceTime you’ll see it doesn’t receive anything but the first click, either).