DAILY NEWS

Stay Ahead, Stay Informed – Every Day

Advertisement
From an Idea to a Hackathon: Lessons from Organizing Build with AI Makerere



After months of planning, countless emails, sponsor outreach, community workshops, and late nights, we successfully hosted the Build with AI Makerere Hackathon in partnership with Google Build with AI and Major League Hacking (MLH).

The event brought together student developers from universities across Uganda to build AI-powered solutions addressing real-world challenges using Gemini, Google AI Studio, and Google Cloud.

Along the way, I learned invaluable lessons about:

Building partnerships and securing sponsorships
Planning and organizing a hackathon from scratch
Leading a growing developer community
Navigating unexpected challenges
πŸ’‘ Creating an environment where students could innovate and learn

This experience reminded me that community leadership isn’t about having everything figured outβ€”it’s about learning, adapting, and bringing people together around a shared vision.

I’ve written a detailed reflection covering the journey, the challenges, the impact, and the lessons I’ll carry into future events.

πŸ‘‰ Read the full story here: build-with-ai-makerere-hackathon

I’d love to hear your thoughts or learn about your own experiences organizing community events!

BuildWithAI GoogleAI GDG @mlhacks Hackathon DeveloperCommunity ArtificialIntelligence OpenSource CommunityBuilding Leadership



Source link

React useDebounce Hook: Debounce State & Callbacks (2026)



You have a search box. The user types react hooks, and your component fires an API request on every single keystroke β€” eleven requests for one query, ten of them already stale by the time they resolve. The fix everyone reaches for is debouncing: wait until the typing stops, then fire once. The fix everyone gets wrong is writing that debounce by hand with setTimeout inside a component, where stale closures, missing cleanup, and re-render churn quietly break it.

useDebounce is the hook that gets it right. This post covers the two shapes you actually need β€” debouncing a value and debouncing a callback β€” when to use each, and how to cancel or flush pending calls. Everything here is the real @reactuses/core API, SSR-safe and typed.

Why Not Just Use setTimeout?

Debouncing itself is simple: delay a function until a quiet period has passed, restarting the timer on every new call. (If you want the full conceptual breakdown β€” and how it differs from throttling β€” see Debounce vs Throttle in React.) The hard part is doing it inside a React component. Here is the naive version, and it has three bugs:

function Search() {
const (query, setQuery) = useState(”);
const timer = useRefReturnTypetypeof setTimeout>>();

function handleChange(e: React.ChangeEventHTMLInputElement>) {
const value = e.target.value;
setQuery(value);
clearTimeout(timer.current);
timer.current = setTimeout(() => {
fetchResults(value); // πŸ› see below
}, 300);
}

return input value={query} onChange={handleChange} />;
}

Enter fullscreen mode

Exit fullscreen mode

It leaks on unmount. If the component unmounts while a timer is pending, the callback still fires 300 ms later β€” often setting state on a gone component, or hitting an API for a screen the user already left.

It captures stale values. The moment you debounce anything other than the raw event value β€” a second piece of state, a prop, a derived value β€” the closure freezes whatever those were when the timer was set, not when it fires.

It spreads. Every place that needs debouncing re-implements the useRef + clearTimeout dance, and each copy is a chance to forget the cleanup.

A hook fixes all three in one place. ReactUse ships two, built on the battle-tested lodash.debounce internally so the edge cases (leading edge, max wait, trailing edge) are already handled.

useDebounce β€” Debounce a Value

The most common case: you have a value that changes rapidly and you want a second, lagging copy of it that only updates after things settle. That second copy is what you feed into expensive work.

import { useState, useEffect } from ‘react’;
import { useDebounce } from ‘@reactuses/core’;

function Search() {
const (query, setQuery) = useState(”);
const debouncedQuery = useDebounce(query, 300);

useEffect(() => {
if (!debouncedQuery) return;
fetchResults(debouncedQuery);
}, (debouncedQuery));

return (
input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder=”Search…”
/>
);
}

Enter fullscreen mode

Exit fullscreen mode

The signature is useDebounce(value, wait?, options?) and it returns the debounced value, with the same type as the input:

const debounced = useDebounce(value, 300);

Enter fullscreen mode

Exit fullscreen mode

The input (query) updates on every keystroke, so the controlled stays perfectly responsive β€” that’s the value you bind to the DOM. The output (debouncedQuery) only catches up 300 ms after the user stops typing, so it’s the value you put in the effect’s dependency array. The API fires once per pause instead of once per keystroke, and your input never feels laggy because the thing you typed into was never the thing being debounced.

This pattern β€” fast value for the UI, debounced value for the side effect β€” is the whole point. Keep them as two separate variables and the rest falls into place.

useDebounceFn β€” Debounce a Callback

Debouncing a value is great when the thing you want to throttle is state. But sometimes you want to debounce an action that takes arguments β€” an autosave, an analytics event, a resize handler β€” without routing it through state first. That’s useDebounceFn:

import { useDebounceFn } from ‘@reactuses/core’;

function Editor({ docId }: { docId: string }) {
const { run } = useDebounceFn((content: string) => {
saveDraft(docId, content);
}, 1000);

return (
textarea onChange={(e) => run(e.target.value)} />
);
}

Enter fullscreen mode

Exit fullscreen mode

useDebounceFn(fn, wait?, options?) returns an object with three members:

const { run, cancel, flush } = useDebounceFn(fn, 1000);

Enter fullscreen mode

Exit fullscreen mode

run β€” the debounced function. Call it as often as you like; fn only actually executes after the calls stop for wait ms. It forwards every argument through, so run(content) calls fn(content).

cancel β€” drop any pending invocation. Nothing fires.

flush β€” fire the pending invocation right now, instead of waiting out the timer.

Crucially, run always calls the latest version of your fn. Internally the hook keeps your callback in a ref, so even though the debounced wrapper is created once, it never goes stale β€” the docId closure problem from the setTimeout version simply doesn’t exist here. And the hook cancels any pending call automatically on unmount, so bug #1 is gone too.

useDebounce is actually built on top of useDebounceFn β€” it debounces a setState call and hands you the resulting value. Same engine, two ergonomics.

cancel and flush in practice

The cancel/flush pair is what raw setTimeout makes painful and a hook makes trivial. Two real cases:

function CommentBox() {
const { run: autosave, cancel, flush } = useDebounceFn(
(text: string) => saveDraft(text),
2000,
);

return (

textarea onChange={(e) => autosave(e.target.value)} />
{/* User hit “Post” β€” persist immediately, don’t wait out the 2s */}
button onClick={() => flush()}>Postbutton>
{/* User hit “Discard” β€” throw away the pending autosave */}
button onClick={() => cancel()}>Discardbutton>
>
);
}

Enter fullscreen mode

Exit fullscreen mode

flush guarantees the in-flight draft is written before the post request goes out; cancel makes sure a discarded draft doesn’t get saved a beat later. Both are one call.

Value or Callback β€” Which One?

A quick decision rule:

Reach for useDebounce when you’re debouncing a piece of state that something else reads β€” a search term, a filter, a slider value feeding a chart. You want a lagging value.
Reach for useDebounceFn when you’re debouncing an action with arguments β€” autosave, logging, firing a network request directly. You want a lagging function, plus cancel/flush control.

If you find yourself creating a piece of state only to debounce it and then immediately fire an effect, useDebounceFn is usually the more direct tool.

Tuning: leading, trailing, and maxWait

The optional third argument is passed straight through to lodash.debounce, so you get its full options object:

useDebounce(value, 300, {
leading: false, // don’t fire on the very first call (default)
trailing: true, // fire after the pause (default)
maxWait: 1000, // …but never wait longer than 1s total
});

Enter fullscreen mode

Exit fullscreen mode

Two knobs worth knowing:

leading: true fires on the first call immediately, then debounces the rest. Good for “respond instantly, then settle” interactions β€” the first click of a button feels snappy while rapid repeats are absorbed.

maxWait caps the total delay. With a pure trailing debounce, a user who types continuously for ten seconds gets zero updates until they stop. maxWait: 1000 forces an update at least once a second even mid-burst β€” the difference between a search box that feels alive and one that feels frozen.

SSR Safety

Both hooks are safe to render on the server. They touch no window, document, or browser timer during render β€” the debounced work only ever runs inside effects, which React never executes on the server. Drop them into a Next.js, Remix, or Astro component and there’s no typeof window guard to write, no hydration warning to chase. (If SSR-safety is a running theme in your codebase, SSR-Safe React Hooks goes deeper.)

The Rate-Limiting Family

useDebounce has three close relatives in ReactUse; pick by what you’re limiting and which shape you need:

The throttle pair mirrors the debounce pair exactly β€” same (value/fn, wait, options) signature, same return shapes β€” but enforces a steady cadence instead of waiting for silence. Use throttle for things that should update during a continuous gesture (scroll position, drag coordinates, a live progress readout); use debounce for things that should update only after it ends (search, autosave, validation). The full mental model is in Debounce vs Throttle in React: When to Use Which.

Takeaways

A hand-rolled setTimeout debounce inside a component ships three bugs by default: it leaks on unmount, it captures stale closures, and it gets copy-pasted.

useDebounce(value, wait) gives you a lagging copy of a value β€” type into the fast one, run effects off the slow one. Perfect for search-as-you-type.

useDebounceFn(fn, wait) debounces an action and hands you { run, cancel, flush }. run always calls your latest callback (no stale closures) and auto-cancels on unmount.
Use flush to commit a pending call early (submit) and cancel to drop it (discard).
The third argument is lodash.debounce options β€” leading for instant-first-call, maxWait to cap the delay so long bursts still update.
Both are SSR-safe and sit alongside useThrottle/useThrottleFn for the fixed-rate case.

Grab them from @reactuses/core and delete your clearTimeout boilerplate.



Source link

CI is the wrong place to first hear about your npm dependencies



Your CI catches the npm vulnerability. Your developer is already three branches away and one standup behind. The package is installed, the lockfile regenerated, the import wired into a service, and the human who made that decision did it on a Tuesday afternoon with a tab open to Stack Overflow. Now the scanner is yelling.

From the terminal, that is not security. That is grief counseling.

That is the frame Sonu Kapoor lays out in a DevOps.com essay this week, and the engineering bones of it are correct.

A scanner is not a gate. It is a status check.

Kapoor’s argument is about feedback loops. A developer installs, codes, commits, pushes. Only then does CI run. By the time the finding surfaces, the decision to add the package, and the context for why, has evaporated. So has the lockfile churn that caused it. What started as “is this package safe?” becomes “fix this in a different sprint.” The scanner did its job. The fix is now a project.

He backs it with a small case study from the NestJS repo: a scan of package-lock.json returned 1,626 resolved packages and 25 vulnerabilities. Of those, 12 were directly fixable. Thirteen were transitive, buried in upstream graphs, waiting on someone else’s release. In a pipeline-first workflow, every dependency hop is a separate commit and a separate run. (Multiply by the number of services your team owns. Then by your runner-minutes budget. Send me the bill.)

The arithmetic gets ugly quickly. A single lockfile with more than fifteen hundred resolved packages is not exotic for a working Node app, it is the default. The chance that the first time anyone looks at that graph is during a pipeline run, after the merge intent is already in the reviewer’s queue, is the structural bug.

Where the essay is right, and where it gets too tidy

Concede the obvious. CI is not the problem. CI is fine. It runs uniformly, it cannot be skipped, and it is the right place to fail a build when an OSV record drops mid-week against a dependency that was clean at merge time. Validation is its native job.

The bug is treating CI as the first place anyone hears about an issue. Discovery there is a category error. You are using the post-flight checklist to taxi the plane.

So far, so unobjectionable. Where the essay gets convenient is the pitch. The cure on offer is a local CLI that Kapoor’s team ships, run against the lockfile before commit, splitting direct from transitive findings, OSV-backed. Useful, probably worth a try. Also: a tool. Tools come and go. A team can adopt this CLI today, lose interest in eight months, and the dependency problem will still be sitting in package-lock.json waiting for the next refactor.

A laptop CLI is the developer-ergonomics half of the answer. It is the half a vendor blog post is built to sell.

What actually moves the gate left

If CI is too late, the answer is not “tell the developer faster on their machine” and walk away. Laptops drift. Branches drift. The version of the scanner that ran on the senior engineer’s machine is not the version that ran on the contractor’s. A discovery step you can opt out of by closing a terminal is not a control. It is a courtesy.

What works, roughly in order of how cheap it is to set up:

A pre-commit hook that fails on a lockfile diff containing a known-bad package. Cheap, local, opt-in, fine as a first line.
A required PR check that runs the same scan against the lockfile before a reviewer is paged. Mandatory, visible inside the diff, blocking the merge instead of decorating the build.
A policy at the branch-protection layer that constrains what is allowed in the lockfile at all: license, registry source, signature, provenance. Unsigned additions to package-lock.json should not be a finding. They should be a closed PR.

Notice CI is in all three. It just stops being where you first find out.

The piece’s case study quietly makes this point against itself. When the only fix for a transitive finding is a chain of iterative upgrades, the question is not “can a developer iterate faster on their laptop?” Yes, obviously. The question is “why was a dependency with thirteen unresolved transitive issues allowed into the merge queue in the first place?”

Verdict

Kapoor is right about the asymmetry. CI is great for validation and structurally bad for discovery on a Node tree where the majority of findings can be someone else’s release schedule. If your security story today is “the pipeline will tell us,” you are not gated. You are notified.

Fix the notification step. Then fix the part where you were ever relying on it.



Source link