Implement a React useColorScheme Hook
Create a custom React hook, useColorScheme, that allows components to easily detect and react to the user's preferred color scheme (light or dark mode) as set in their operating system preferences. This hook will provide a dynamic way to manage theme-dependent UI elements in your React applications.
Problem Description
Your task is to implement a custom React hook named useColorScheme. This hook should:
- Detect the initial color scheme: On initial render, determine whether the user's operating system preference is for a "light" or "dark" color scheme.
- Listen for changes: Subscribe to changes in the user's color scheme preference. When the preference changes, the hook should update its returned value.
- Return the current color scheme: The hook should return a string indicating the current color scheme, either
"light"or"dark".
You should leverage the window.matchMedia API, specifically the (prefers-color-scheme: dark) media query, to achieve this.
Key Requirements:
- The hook must be written in TypeScript.
- It should be a functional component hook.
- The returned value should be a string:
"light"or"dark". - The hook must correctly handle the initial detection of the color scheme.
- The hook must re-render components that use it when the user's preference changes.
- The hook should clean up any event listeners when the component unmounts to prevent memory leaks.
Expected Behavior:
- When the user's OS is set to dark mode, the hook should return
"dark". - When the user's OS is set to light mode, the hook should return
"light". - If the OS setting is not explicitly detected (e.g., not supported or no preference set), a default value (e.g.,
"light") should be returned. - When the user toggles their OS theme preference, any component using
useColorSchemeshould automatically update to reflect the new theme.
Edge Cases:
- Browser/OS Support: What happens if
window.matchMediais not available or theprefers-color-schemequery is not supported? - No Preference: What if the user has not set a specific preference?
- Server-Side Rendering (SSR): While this hook is primarily client-side, consider how its initial value might behave during SSR (though a full SSR solution might be out of scope for this challenge, it's good to be aware).
Examples
Example 1:
// Assume user's OS is set to dark mode.
const MyComponent = () => {
const colorScheme = useColorScheme(); // Returns "dark"
return (
<div className={colorScheme}>
<h1>Welcome!</h1>
<p>Your current theme is: {colorScheme}</p>
</div>
);
};
Output (when OS is dark):
The colorScheme variable will be "dark". The rendered HTML might look like:
<div class="dark">
<h1>Welcome!</h1>
<p>Your current theme is: dark</p>
</div>
Explanation:
The useColorScheme hook detects the OS preference and returns "dark".
Example 2:
// Assume user's OS is set to light mode.
const AnotherComponent = () => {
const theme = useColorScheme(); // Returns "light"
return (
<button style={{ backgroundColor: theme === 'dark' ? '#333' : '#eee' }}>
Toggle Theme
</button>
);
};
Output (when OS is light):
The theme variable will be "light". The button's background will be a light color.
Explanation:
The useColorScheme hook detects the OS preference and returns "light".
Example 3: Handling a Theme Change
Imagine a user is running an application that uses useColorScheme.
- Initially, their OS is set to light mode.
useColorScheme()returns"light". - Later, the user changes their OS setting to dark mode while the application is still running.
- The
useColorSchemehook detects this change.
Expected Behavior:
The component using useColorScheme should automatically re-render, and the colorScheme variable should now be "dark".
Constraints
- The hook must be implemented using TypeScript.
- It should not rely on any external state management libraries like Redux or Zustand for its core functionality.
- The solution should be efficient and avoid unnecessary re-renders of consuming components, beyond what React's
useStateanduseEffectnaturally provide. - The solution should be compatible with modern React versions (17+).
Notes
- The
window.matchMediaAPI returns aMediaQueryListobject. You'll need to listen to itschangeevent. - The initial state of the hook should be determined by checking the
matchesproperty of theMediaQueryListwhen the hook first runs. - Remember to handle cleanup in
useEffectto remove the event listener when the component unmounts. - Consider how to handle browsers that might not support
window.matchMediaor the specific media query. A sensible default (e.g.,"light") is a good practice.