Back to Machine Coding
Machine Coding
easy
mid

How would you build a weather widget component in React?

Standard build: a card that fetches current weather for a location and shows temperature, conditions, icon. Use React Query for fetching with caching. Geolocation API for current location with fallback to a default city. Loading + error states. Show units toggle (C/F). Optionally hourly forecast. For interviews: name the API (OpenWeather/WeatherAPI), explain caching, handle the offline case.

8 min read·~30 min to think through

Common build prompt. Below is a complete implementation with the patterns interviewers expect.

API

OpenWeatherMap is the typical choice (free tier, simple).

ts
GET https://api.openweathermap.org/data/2.5/weather?q=London&units=metric&appid=KEY

Component

tsx
import { useQuery } from '@tanstack/react-query';
import { useState, useEffect } from 'react';

type Weather = {
  name: string;
  main: { temp: number; humidity: number };
  weather: { description: string; icon: string }[];
};

async function fetchWeather(city: string, units: 'metric' | 'imperial'): Promise<Weather> {
  const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=${units}&appid=${KEY}`;
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Weather fetch failed: ${res.status}`);
  return res.json();
}

function useGeolocation() {
  const [coords, setCoords] = useState<GeolocationCoordinates | null>(null);
  useEffect(() => {
    navigator.geolocation.getCurrentPosition(
      pos => setCoords(pos.coords),
      () => setCoords(null),
    );
  }, []);
  return coords;
}

function WeatherWidget({ defaultCity = 'London' }: { defaultCity?: string }) {
  const [city, setCity] = useState(defaultCity);
  const [units, setUnits] = useState<'metric' | 'imperial'>('metric');

  const { data, isLoading, isError, refetch } = useQuery({
    queryKey: ['weather', city, units],
    queryFn: () => fetchWeather(city, units),
    staleTime: 5 * 60 * 1000,           // 5 min — weather doesn't change every second
    refetchOnWindowFocus: false,
    retry: 1,
  });

  if (isLoading) return <Skeleton />;
  if (isError) return <ErrorState onRetry={refetch} />;
  if (!data) return null;

  return (
    <div className="weather-card">
      <header>
        <h2>{data.name}</h2>
        <button onClick={() => setUnits(u => u === 'metric' ? 'imperial' : 'metric')}>
          {units === 'metric' ? '°C' : '°F'}
        </button>
      </header>
      <img
        src={`https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png`}
        alt={data.weather[0].description}
      />
      <div className="temp">{Math.round(data.main.temp)}°</div>
      <div className="conditions">{data.weather[0].description}</div>
      <div className="humidity">Humidity: {data.main.humidity}%</div>
      <form onSubmit={e => { e.preventDefault(); setCity(new FormData(e.currentTarget).get('city') as string); }}>
        <input name="city" placeholder="Change city" defaultValue={city} />
      </form>
    </div>
  );
}

Geolocation integration

tsx
const coords = useGeolocation();

const { data } = useQuery({
  queryKey: ['weather', coords?.latitude, coords?.longitude, units],
  queryFn: () => fetchWeatherByCoords(coords!.latitude, coords!.longitude, units),
  enabled: coords !== null,
});

Caching strategy

  • staleTime 5 min: weather is slow-moving. Browser doesn't refetch on every render.
  • localStorage cache: persist last-known-good response so the widget shows something even offline.
  • CDN cache: if you proxy the API server-side, set Cache-Control to share across users.

Error / loading states

  • Loading: skeleton with the same shape as the final card — no layout shift.
  • Error: friendly message with a retry button. Don't show the raw error.
  • Offline: show last cached value with a 'stale' badge.
tsx
if (!navigator.onLine && cached) {
  return <Card data={cached} badge="Offline — last updated 2h ago" />;
}

Accessibility

  • Icon has alt text from the API's description.
  • Temperature is announced clearly (aria-label).
  • The units toggle is a real button with aria-pressed.

Extensions for senior-level

  • Hourly forecast: /forecast endpoint + small chart (recharts).
  • Background fetch: react-query refetchInterval if you want auto-refresh.
  • Multiple cities: tabs or a list, each with its own query.
  • Theming by conditions: clear=blue, rain=grey background.
  • Service Worker: cache API responses for offline + faster repeat loads.

What interviewers look for

  1. Naming a real API (not handwaving fetch('/weather')).
  2. Using a server-state library (React Query) rather than useEffect + fetch.
  3. Caching strategy with staleTime.
  4. Loading + error + offline handling.
  5. Units toggle and city change without re-mounting the widget.
  6. Accessibility basics.

Follow-up questions

  • How would you cache for offline use?
  • Why React Query over useEffect + fetch here?
  • How would you add an hourly forecast chart?

Common mistakes

  • Re-fetching on every render with useEffect + fetch.
  • Hard-coding the API key in client code (use env vars; better: proxy server-side).
  • Forgetting loading + error states.

Performance considerations

  • React Query handles dedup, retry, and stale-while-revalidate. Cache for at least 5 minutes — weather doesn't change faster. CDN edge cache further reduces upstream calls if you proxy.

Edge cases

  • User denies geolocation — fall back to default city, not a crash.
  • API rate limit (free tier ~60 req/min) — cache aggressively.
  • Offline — last cached response or skeleton, not a white card.

Real-world examples

  • Apple's weather widget, Google search weather card, every dashboard app's location module. Common in onboarding test takehome assignments.

Senior engineer discussion

Senior framing: it's not 'a weather card' — it's a network-dependent widget with caching, offline awareness, geolocation fallback, unit conversion, and a11y. Show that you think through all of those even on a simple prompt.

Related questions