JavaScript and React course Β· Module 3: State and Events

Project: Martian Colony Dashboard

4 min read
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:

  1. Colony Systems Panel - monitors for atmosphere, power, water, and temperature
  2. Control Center - buttons for manually adjusting parameters
  3. Astronaut Registration Form - a controlled form for adding crew members
  4. Event Log - a log that every panel writes to through the addLogEntry function 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

  1. useState - minimum 4 states (atmosphere, power, water, temperature) + crew state + form state
  2. Event handling - buttons adjusting parameters with safeguards (min/max)
  3. Controlled form - adding new astronauts with validation
  4. Alarms - checking the critical state in handlers and writing to the log
  5. Lifting state up - shared event log accessible from multiple components
  6. 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?

Useful articles