On our cosmic journey through React, our ship must connect with many different systems - space stations, satellites, planetary bases. Each of these systems has its own communication protocol. The Adapter and Facade patterns are like a universal translator that allows our ship to communicate with any system without needing to know its internal details.
The Adapter pattern (also known as Wrapper) allows objects with incompatible interfaces to work together. In React, we most commonly use it to wrap external APIs with a consistent, predictable interface.
Imagine that your cosmic weather database for different planets uses three different API providers. Each of them returns data in a different format. An Adapter allows you to unify this data:
1// Raw responses from different space APIs
2// Mars API returns: { temp_celsius: -60, wind_kph: 25, condition: "dusty" }
3// Jupiter API returns: { temperature: { value: -145, unit: "C" }, windSpeed: 350 }
4// Saturn API returns: { readings: { t: -178, w: 500 }, status: "stormy" }Without an adapter, we would need to check in every component which API the data came from and process it accordingly. That leads to a mess!
Let's create adapters that unify data from different APIs:
1// Unified weather data interface
2// { planet, temperature, windSpeed, condition }
3
4function adaptMarsWeather(rawData) {
5 return {
6 planet: 'Mars',
7 temperature: rawData.temp_celsius,
8 windSpeed: rawData.wind_kph,
9 condition: rawData.condition,
10 };
11}
12
13function adaptJupiterWeather(rawData) {
14 return {
15 planet: 'Jupiter',
16 temperature: rawData.temperature.value,
17 windSpeed: rawData.windSpeed,
18 condition: rawData.windSpeed > 300 ? 'extreme' : 'moderate',
19 };
20}
21
22function adaptSaturnWeather(rawData) {
23 return {
24 planet: 'Saturn',
25 temperature: rawData.readings.t,
26 windSpeed: rawData.readings.w,
27 condition: rawData.status,
28 };
29}Now we have three simple functions, each of which takes raw data and returns an object with an identical structure. Our weather component can use this data without knowing where it came from:
1function PlanetWeatherCard({ weatherData }) {
2 // weatherData ALWAYS has the same shape thanks to adapters
3 return (
4 <div className="weather-card">
5 <h3>{weatherData.planet}</h3>
6 <p>Temperature: {weatherData.temperature} C</p>
7 <p>Wind: {weatherData.windSpeed} km/h</p>
8 <p>Condition: {weatherData.condition}</p>
9 </div>
10 );
11}Adapters work great as custom hooks that wrap external libraries:
1// Adapter for different space map libraries
2function useSpaceMap(provider) {
3 const [map, setMap] = useState(null);
4
5 useEffect(() => {
6 let mapInstance;
7
8 if (provider === 'galactic-maps') {
9 // GalacticMaps library has its own API
10 mapInstance = new GalacticMaps.Chart('#map');
11 mapInstance.init({ zoom: 5 });
12 } else if (provider === 'star-atlas') {
13 // StarAtlas has a completely different API
14 mapInstance = StarAtlas.create(document.getElementById('map'), {
15 level: 5,
16 mode: '3d',
17 });
18 }
19
20 // We return a unified interface
21 setMap({
22 zoomIn: () => {
23 if (provider === 'galactic-maps') mapInstance.setZoom(mapInstance.getZoom() + 1);
24 else mapInstance.changeLevel(mapInstance.level + 1);
25 },
26 zoomOut: () => {
27 if (provider === 'galactic-maps') mapInstance.setZoom(mapInstance.getZoom() - 1);
28 else mapInstance.changeLevel(mapInstance.level - 1);
29 },
30 centerOn: (coords) => {
31 if (provider === 'galactic-maps') mapInstance.panTo(coords);
32 else mapInstance.focus({ x: coords.lat, y: coords.lng });
33 },
34 });
35
36 return () => {
37 if (provider === 'galactic-maps') mapInstance.destroy();
38 else mapInstance.dispose();
39 };
40 }, [provider]);
41
42 return map;
43}Thanks to this hook, the component using the map doesn't need to know which library is being used underneath. If we wanted to change the map provider, we would only need to modify the hook - no component would change.
The Facade pattern provides a simplified interface to a complex subsystem. In React, we most commonly implement it as hooks that hide complicated logic behind a simple API.
Think of a spaceship's control panel. The pilot doesn't need to know the details of how each system works - they press the "Start engines" button and the entire complex process starts automatically. A Facade works exactly the same way.
Without a facade, a component has to manage many details itself:
1// WITHOUT Facade - the component is overwhelmed with details
2function MissionLauncher() {
3 const [fuel, setFuel] = useState(0);
4 const [systems, setSystems] = useState([]);
5 const [crewReady, setCrewReady] = useState(false);
6 const [countdown, setCountdown] = useState(null);
7 const [launchStatus, setLaunchStatus] = useState('idle');
8 const [errors, setErrors] = useState([]);
9
10 // Checking fuel
11 useEffect(() => {
12 fetchFuelLevel().then(setFuel);
13 }, []);
14
15 // Checking systems
16 useEffect(() => {
17 checkAllSystems().then(setSystems);
18 }, []);
19
20 // Countdown
21 useEffect(() => {
22 if (countdown !== null && countdown > 0) {
23 const timer = setTimeout(() => setCountdown(c => c - 1), 1000);
24 return () => clearTimeout(timer);
25 }
26 if (countdown === 0) {
27 executeLaunch().then(() => setLaunchStatus('launched'));
28 }
29 }, [countdown]);
30
31 const startLaunch = async () => {
32 if (fuel < 80) { setErrors(e => [...e, 'Not enough fuel']); return; }
33 if (!crewReady) { setErrors(e => [...e, 'Crew not ready']); return; }
34 const failedSystems = systems.filter(s => s.status !== 'ok');
35 if (failedSystems.length > 0) {
36 setErrors(e => [...e, 'Systems not operational']);
37 return;
38 }
39 setCountdown(10);
40 };
41
42 // ... rest of component with a huge amount of logic
43}With a facade, we move all this logic into a hook:
1// WITH Facade - the hook hides all the complexity
2function useMissionLaunch() {
3 const [fuel, setFuel] = useState(0);
4 const [systems, setSystems] = useState([]);
5 const [crewReady, setCrewReady] = useState(false);
6 const [countdown, setCountdown] = useState(null);
7 const [status, setStatus] = useState('idle');
8 const [errors, setErrors] = useState([]);
9
10 useEffect(() => {
11 fetchFuelLevel().then(setFuel);
12 checkAllSystems().then(setSystems);
13 }, []);
14
15 useEffect(() => {
16 if (countdown !== null && countdown > 0) {
17 const timer = setTimeout(() => setCountdown(c => c - 1), 1000);
18 return () => clearTimeout(timer);
19 }
20 if (countdown === 0) {
21 executeLaunch().then(() => setStatus('launched'));
22 }
23 }, [countdown]);
24
25 const launch = () => {
26 const newErrors = [];
27 if (fuel < 80) newErrors.push('Not enough fuel');
28 if (!crewReady) newErrors.push('Crew not ready');
29 const failedSystems = systems.filter(s => s.status !== 'ok');
30 if (failedSystems.length > 0) newErrors.push('Systems not operational');
31
32 if (newErrors.length > 0) {
33 setErrors(newErrors);
34 return;
35 }
36 setCountdown(10);
37 setStatus('countdown');
38 };
39
40 const abort = () => {
41 setCountdown(null);
42 setStatus('aborted');
43 };
44
45 return {
46 // Simple, readable API
47 launch,
48 abort,
49 status,
50 countdown,
51 errors,
52 isReady: fuel >= 80 && crewReady && systems.every(s => s.status === 'ok'),
53 fuelLevel: fuel,
54 setCrewReady,
55 };
56}
57
58// The component is now clean and simple
59function MissionLauncher() {
60 const mission = useMissionLaunch();
61
62 return (
63 <div>
64 <h1>Mission Launch</h1>
65 <p>Fuel: {mission.fuelLevel}%</p>
66 <p>Status: {mission.status}</p>
67 {mission.countdown !== null && <p>Countdown: {mission.countdown}</p>}
68 {mission.errors.map((err, i) => <p key={i} style={{color: 'red'}}>{err}</p>)}
69 <button onClick={mission.launch} disabled={!mission.isReady}>
70 Launch
71 </button>
72 <button onClick={mission.abort}>Abort</button>
73 </div>
74 );
75}Facade is particularly useful when you need to coordinate multiple operations:
1function useCrewManagement(missionId) {
2 // Internally manages multiple data sources
3 const [crew, setCrew] = useState([]);
4 const [assignments, setAssignments] = useState({});
5 const [healthRecords, setHealthRecords] = useState({});
6
7 useEffect(() => {
8 Promise.all([
9 fetchCrew(missionId),
10 fetchAssignments(missionId),
11 fetchHealthRecords(missionId),
12 ]).then(([crewData, assignData, healthData]) => {
13 setCrew(crewData);
14 setAssignments(assignData);
15 setHealthRecords(healthData);
16 });
17 }, [missionId]);
18
19 // Simple interface for the component
20 return {
21 crewMembers: crew.map(member => ({
22 ...member,
23 role: assignments[member.id]?.role || 'Unassigned',
24 healthStatus: healthRecords[member.id]?.status || 'Unknown',
25 })),
26 assignRole: (memberId, role) => {
27 updateAssignment(missionId, memberId, role)
28 .then(() => setAssignments(prev => ({
29 ...prev,
30 [memberId]: { ...prev[memberId], role },
31 })));
32 },
33 isCrewReady: crew.length > 0 && crew.every(m =>
34 assignments[m.id]?.role && healthRecords[m.id]?.status === 'fit'
35 ),
36 };
37}| Feature | Adapter | Facade | |---------|---------|--------| | Purpose | Unify incompatible interfaces | Simplify a complex system | | When | Multiple data sources with different formats | Complex logic to hide | | In React | Wrapping external APIs/libraries | Custom hooks hiding complexity | | Space analogy | Alien civilization language translator | Ship control panel |
Both patterns can be combined - a Facade can internally use adapters:
1function useGalacticWeather(planets) {
2 // Facade: simplifies the interface
3 const [weather, setWeather] = useState({});
4
5 useEffect(() => {
6 const adapters = {
7 mars: adaptMarsWeather,
8 jupiter: adaptJupiterWeather,
9 saturn: adaptSaturnWeather,
10 };
11
12 // Adapter: unifies data from different APIs
13 Promise.all(
14 planets.map(planet =>
15 fetchWeather(planet).then(raw => adapters[planet](raw))
16 )
17 ).then(results => {
18 const weatherMap = {};
19 results.forEach(w => { weatherMap[w.planet] = w; });
20 setWeather(weatherMap);
21 });
22 }, [planets]);
23
24 return {
25 getWeather: (planet) => weather[planet] || null,
26 isLoaded: Object.keys(weather).length === planets.length,
27 coldestPlanet: Object.values(weather)
28 .sort((a, b) => a.temperature - b.temperature)[0]?.planet || null,
29 };
30}The Adapter and Facade patterns are essential tools in the arsenal of a cosmic React programmer:
Just as a universal power adapter allows a spaceship to connect to any docking station in the galaxy, the Adapter and Facade patterns allow our React components to work with any system without unnecessary complications.