photo-search: The Pre-Hooks React App That Searches as You Type

A small Pixabay browser becomes a snapshot of 2018-era React: class components, controlled inputs, old Material-UI, and a live-search pattern that feels great until you ask what happens at scale.

8 min read • View on GitHub • More from zubair-trabzada

A person typing into a search box that is tethered to a row of image cards by a taut stream of sparks and envelopes. The scene shows every keystroke traveling outward into fresh results, explaining the app’s immediate live-search feel and the hidden cost of constant requests.
The whole app is a feedback loop: type, fetch, redraw, repeat.
Key Takeaways

The best thing about photo-search is not that it searches photos. It is that the app feels like it is listening. Type a letter, and it reacts immediately, replacing the quiet act of search with a running conversation between your keyboard and the Pixabay API.

That sensation is the whole article. Beneath it is a small, tidy React app built in the pre-Hooks era, with class components, controlled inputs, and Material-UI conventions that now read like a time capsule.

A Search Box That Behaves Like a Living Thing

Open the app and the first thing you notice is the tempo. Each change in the search field triggers a request, so the page responds before your hands leave the keyboard. It is the kind of interaction that feels premium in a demo and slightly reckless in production.

That trade-off is the point. The repo makes live search look effortless because the surface area is small: one input, one API, one gallery, one modal. There is no search button, no submit state, no buffering layer between thought and result.

onTextChange = e => {
  this.setState(
    { [e.target.name]: e.target.value },
    () => {
      axios
        .get(`https://pixabay.com/api/?key=${process.env.REACT_APP_PIXABAY_API_KEY}&q=${this.state.searchText}&image_type=photo&per_page=${this.state.amount}&safesearch=true`)
        .then(res => this.setState({ images: res.data.hits }))
        .catch(err => console.log(err));
    }
  );
};

That callback matters. The component waits for setState to settle before issuing the request, which keeps the query aligned with the latest input. It is a classic class-component move: explicit, slightly ceremonial, and very much of its era.

The Brain Lives in One Class Component

One class component owns the search loop, while a separate branch handles image zoom. The diagram makes the timing visible: input updates state, state triggers fetch, fetch replaces results.

Search.js is the center of gravity. It owns the query string, the requested result count, and the image array that gets rendered below the fold. It also decides when the gallery exists at all, using conditional rendering so the results component stays out of the tree until there is something to show.

That keeps the architecture simple. ImageResults only appears after results arrive, and the gallery itself handles a separate concern: local modal state for the enlarged image. The search component is the fetch engine. The results component is the viewer.

{this.state.images.length > 0 ? (
  <ImageResults images={this.state.images} />
) : null}
A split interior scene shows a control-room-like Search component on one side and a gallery wall on the other. The left side manages text input, state counters, and outgoing requests, while the right side turns image data into tiles and a framed lightbox, explaining how the app separates fetching from display.
The app is clean because each component has one job: Search drives requests, ImageResults handles presentation and zoom.

Why This Looks Like 2018

The repo reads like a frozen front-end moment. Class components carry state. Material-UI v0.x sits beside Bootstrap. MuiThemeProvider wraps the app because that was the way to make the component library behave.

None of that is sloppy. It is just historically legible. You can see the ecosystem in transition: Create React App boilerplate, old styling conventions, a separate service worker file, and the assumption that a polished interface could be assembled from a few well-chosen libraries rather than a framework stack.

That is what makes the project interesting. It is not a museum piece because it is old. It is a museum piece because its choices map so cleanly to a specific set of front-end assumptions that have since moved on.

The UI Is Simple Because the Data Model Is Simple

Pixabay returns a predictable JSON payload. The app turns that payload into a grid, then turns one selected image into a modal. There is no heavy transformation layer, no client-side indexing, no search engine of its own. The UI stays light because the data contract stays light.

That split is smart. Search resolves the query. ImageResults decides how the gallery should look. The result is a pleasant separation between network work and interface work, which is why the app can feel more polished than its internals suggest.

In practical terms, the gallery is doing two jobs at once: it renders the thumbnail wall and manages zoom state. That is fine here because the interaction surface is tiny, but it also shows the limits of the pattern. The component is easy to read because the problem is small.

DimensionThis repoA modern photo search app
Search triggerEvery keystroke fires a fetchDebounced input, submit, or intent-based search
State modelClass component state and callbacksHooks, reducers, or query libraries
UI systemOld Material-UI plus BootstrapCurrent component systems with cleaner theming
Request handlingDirect Axios call from the input handlerCached queries, aborts, retries, and debounce
Production readinessGreat for a demo, thin for scaleBuilt for rate limits, latency, and partial failure
Ecosystem signalPre-Hooks React habitsComposable, hook-first front-end norms

The Fast Path Has a Cost

The app’s biggest weakness is also its most visible strength. No debounce means more requests, more churn, and more chances to hit API limits or waste bandwidth on intermediate keystrokes. If the user types quickly, the network does too.

That is acceptable in a portfolio project because the experience is the pitch. The app is showing you that live feedback can feel better than a button click. But the missing guardrail is impossible to ignore once you think about scale, cost, or rate limiting.

QuestionLive search hereProduction-safe approach
When does search run?On every input changeAfter a short pause or explicit submit
How many requests happen?Potentially one per characterFewer, coalesced requests
What happens to stale queries?They can still completeThey are often canceled or ignored
What does the user feel?Instant feedbackIntentional feedback with a small delay
What does the backend feel?Pressure from bursty trafficLess noise and lower waste

This is the difference between a demo and a system. A demo asks whether the interaction feels good. A system asks whether the interaction survives contact with real users.

What Modern Photo Search Would Do Differently

A newer photo search tool would probably move in one of two directions. It would either narrow the scope and keep everything local, or it would go bigger and add semantic search, embeddings, caching, and more defensive request handling.

That is where projects like Immich, PhotoPrism, PictoPy, or local AI search tools diverge from this repo. They are not just showing images. They are trying to make photo retrieval smarter, safer, or more private. photo-search is simpler than that. It is a clean browser over an external API, and that simplicity is its identity.

So the comparison is not about feature count. It is about philosophy. This repo optimizes for immediate visual feedback. Modern tools optimize for trust, scale, and relevance.

The Small Repo That Explains a Big Shift

photo-search is tiny, but it explains a lot. It shows a moment when React apps were often stateful classes, when library choices were less standardized, and when the fastest way to make something feel alive was to wire input directly to fetch.

That design still works as a demo because the code is straightforward and the interaction is legible. But the same simplicity also exposes what the ecosystem later learned to hide: the cost of immediacy, the value of debounce, and the usefulness of more composable component patterns.

In that sense, the repo is less a photo app than a before-and-after slide. It shows how much front-end practice changed without needing to say so out loud.