JavaScript and React course Β· Module 3: State and Events
Project: Martian Colony Dashboard
In this lesson5
Congratulations - you have reached the final project of the "State and Events" module! In previous lessons, you learned about useState, event handling, data flow, lifting state up, and controlled forms. Now you will combine all that knowledge into one cohesive application.
Your task is to build a Martian Colony Dashboard - an interactive panel for managing a base on Mars. Ra, your cosmic guide, has gathered the requirements from command headquarters. Mission: write a React application that monitors and controls key colony systems.
What you are building
The dashboard consists of four connected sections:
- Colony Systems Panel - monitors for atmosphere, power, water, and temperature
- Control Center - buttons for manually adjusting parameters
- Astronaut Registration Form - a controlled form for adding crew members
- Event Log - a log that every panel writes to through the
addLogEntryfunction from props
All sections must work together - changes in one panel affect the others.
Technical requirements
1. Multiple states (useState)
Each colony system has its own state:
1const [atmosphere, setAtmosphere] = useState(78);
2const [power, setPower] = useState(85);
3const [water, setWater] = useState(62);
4const [temperature, setTemperature] = useState(-10);
5const [crew, setCrew] = useState([
6 { id: 1, name: 'Anna Kowalska', role: 'Commander' },
7 { id: 2, name: 'Jan Wisniewski', role: 'Engineer' }
8]);2. Event handling
Each system has adjustment buttons. Remember the safe ranges:
1const handleAtmosphereBoost = () => {
2 setAtmosphere(prev => Math.min(100, prev + 5));
3 addLogEntry('Increased O2 level by 5%');
4};3. Controlled registration form
A form for adding new astronauts to the crew:
1const [newCrew, setNewCrew] = useState({ name: '', role: '' });
2
3const handleCrewChange = (event) => {
4 const { name, value } = event.target;
5 setNewCrew(prev => ({ ...prev, [name]: value }));
6};
7
8const handleCrewSubmit = (event) => {
9 event.preventDefault();
10 if (!newCrew.name.trim()) return;
11 setCrew(prev => [...prev, { id: Date.now(), ...newCrew }]);
12 setNewCrew({ name: '', role: '' });
13};4. Alarms in event handlers
Check the critical state in the handler that changes the value - the alarm appears exactly at the moment of the change:
1const changePower = (delta) => {
2 const next = Math.min(100, Math.max(0, power + delta));
3 setPower(next);
4 if (next < 20) {
5 addLogEntry('ALARM: Critically low power level!');
6 }
7};Changes over time (e.g. water consumption every 5 seconds) need the useEffect hook with a cleanup - you will learn it in the module on effects and then extend this dashboard.
5. Lifting state up - shared event log
The log state and addLogEntry function live in the main component and are passed down:
1function MarsColonyDashboard() {
2 const [log, setLog] = useState([]);
3
4 const addLogEntry = (message) => {
5 setLog(prev => [
6 { id: Date.now(), time: new Date().toLocaleTimeString(), message },
7 ...prev
8 ].slice(0, 20));
9 };
10
11 return (
12 <div>
13 <SystemsPanel onLog={addLogEntry} />
14 <CrewPanel onLog={addLogEntry} />
15 <EventLog entries={log} />
16 </div>
17 );
18}Component structure
This is what the component tree can look like - the state lives at the top, and the panels get it through props:
1MarsColonyDashboard (main state)
2βββ SystemsPanel (props: values + callbacks)
3β βββ SystemGauge Γ 4
4βββ CrewPanel (props: crew, form, callbacks)
5β βββ CrewList
6β βββ CrewForm
7βββ EventLog (props: entries)Data flows down through props, events flow up through callbacks - the heart of unidirectional data flow in React!
Evaluation criteria
- useState - minimum 4 states (atmosphere, power, water, temperature) + crew state + form state
- Event handling - buttons adjusting parameters with safeguards (min/max)
- Controlled form - adding new astronauts with validation
- Alarms - checking the critical state in handlers and writing to the log
- Lifting state up - shared event log accessible from multiple components
- Componentization - minimum 3 separate components communicating through props
Starter skeleton
To get started, you get an app skeleton that follows the structure above: MarsColonyDashboard holds the states and the log, and the panels receive data and functions through props. The atmosphere and power buttons already work and write entries to the log - fill in the places marked TODO: the water and temperature states, the 0-100 limits, the low power alarm and the astronaut registration form.
Code for this lesson: App.jsx
1import { useState } from 'react';
2
3// Skeleton of the "Martian Colony Dashboard" project (lesson 3_7).
4// The states and the log live in MarsColonyDashboard, and the panels get data and functions through props.
5// Fill in the places marked TODO.
6
7let nextLogId = 1;
8
9function SystemGauge({ label, value, unit, onAdjust }) {
10 return (
11 <div style={styles.gauge}>
12 <h3 style={styles.gaugeLabel}>{label}</h3>
13 <p style={styles.gaugeValue}>{value}{unit}</p>
14 <button style={styles.button} onClick={() => onAdjust(-5)}>-5</button>
15 <button style={styles.button} onClick={() => onAdjust(5)}>+5</button>
16 </div>
17 );
18}
19
20function SystemsPanel({ atmosphere, power, onAtmosphereChange, onPowerChange }) {
21 return (
22 <section style={styles.panel}>
23 <h2 style={styles.title}>Colony systems</h2>
24 <div style={styles.gauges}>
25 <SystemGauge label="Atmosphere (O2)" value={atmosphere} unit="%" onAdjust={onAtmosphereChange} />
26 <SystemGauge label="Power" value={power} unit="%" onAdjust={onPowerChange} />
27 {/* TODO: water (%) and temperature (Β°C) gauges */}
28 </div>
29 </section>
30 );
31}
32
33function CrewPanel({ crew }) {
34 // TODO: a controlled form { name, role } with e.preventDefault() and validation.
35 // A function from props adds the new astronaut, because the crew state lives in the parent.
36 return (
37 <section style={styles.panel}>
38 <h2 style={styles.title}>Crew ({crew.length})</h2>
39 <ul style={styles.list}>
40 {crew.map((member) => (
41 <li key={member.id}>{member.name} - {member.role}</li>
42 ))}
43 </ul>
44 </section>
45 );
46}
47
48function EventLog({ entries }) {
49 return (
50 <section style={styles.panel}>
51 <h2 style={styles.title}>Event log</h2>
52 {entries.length === 0 ? (
53 <p style={styles.muted}>No entries yet - use the buttons in the systems panel.</p>
54 ) : (
55 <ul style={styles.list}>
56 {entries.map((entry) => (
57 <li key={entry.id}>[{entry.time}] {entry.message}</li>
58 ))}
59 </ul>
60 )}
61 </section>
62 );
63}
64
65function MarsColonyDashboard() {
66 const [atmosphere, setAtmosphere] = useState(78);
67 const [power, setPower] = useState(85);
68 // TODO: the water (62) and temperature (-10) states
69 const [crew, setCrew] = useState([
70 { id: 1, name: 'Anna Kowalska', role: 'Commander' },
71 { id: 2, name: 'Jan Wisniewski', role: 'Engineer' },
72 ]);
73 const [log, setLog] = useState([]);
74
75 // Shared log: the panels add entries through this function passed in props
76 const addLogEntry = (message) => {
77 const entry = { id: nextLogId++, time: new Date().toLocaleTimeString(), message };
78 setLog((prev) => [entry, ...prev].slice(0, 20));
79 };
80
81 const changeAtmosphere = (delta) => {
82 // TODO: keep the value within 0-100 (Math.min and Math.max)
83 setAtmosphere(atmosphere + delta);
84 addLogEntry('Atmosphere: ' + (delta > 0 ? '+' : '') + delta + '%');
85 };
86
87 const changePower = (delta) => {
88 // TODO: the 0-100 range and an alarm in the log when power drops below 20%
89 setPower(power + delta);
90 addLogEntry('Power: ' + (delta > 0 ? '+' : '') + delta + '%');
91 };
92
93 // TODO: an addCrewMember(member) function that adds the astronaut with setCrew
94 // and writes an entry to the log; pass it to CrewPanel
95
96 return (
97 <div style={styles.app}>
98 <h1 style={styles.header}>Martian Colony Dashboard</h1>
99 <SystemsPanel
100 atmosphere={atmosphere}
101 power={power}
102 onAtmosphereChange={changeAtmosphere}
103 onPowerChange={changePower}
104 />
105 <CrewPanel crew={crew} />
106 <EventLog entries={log} />
107 </div>
108 );
109}
110
111const styles = {
112 app: { fontFamily: 'Arial, sans-serif', background: '#0b1020', color: '#e6ecff', minHeight: '100vh', padding: '20px' },
113 header: { color: '#ff8c42', textAlign: 'center', marginTop: 0 },
114 panel: { background: '#151c33', borderRadius: '12px', padding: '16px', marginBottom: '16px' },
115 title: { marginTop: 0, color: '#7fd1ff' },
116 gauges: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: '12px' },
117 gauge: { background: '#1e2747', borderRadius: '10px', padding: '12px', textAlign: 'center' },
118 gaugeLabel: { margin: '0 0 6px', fontSize: '15px' },
119 gaugeValue: { margin: '0 0 10px', fontSize: '28px', fontWeight: 'bold' },
120 button: { margin: '0 4px', padding: '6px 12px', border: 'none', borderRadius: '6px', background: '#3a4f9a', color: 'white', cursor: 'pointer' },
121 list: { margin: 0, paddingLeft: '20px', lineHeight: 1.7 },
122 muted: { color: '#8f9bbf', margin: 0 },
123};
124
125export default function App() {
126 return <MarsColonyDashboard />;
127}Good luck, pilot! This project is your first real mission in the world of React - show that you can manage a colony on Mars. Remember: every panel is a separate ship module, but data and decisions flow through one commander - the main component.
Spotted a mistake in this lesson?