Building an Experiment Dashboard with React
So far you've built structure with HTML, added behavior with JavaScript, created servers with Node.js and Express, and completed APIs that store and retrieve experiment data from databases. But as screens grow complex, problems arise. Gene info cards, sample lists, QC result tables, filter buttons โ managing all of this in a single HTML file with innerHTML quickly leads to thousands of lines of code.
React solves this problem. It's the technology for creating your own custom HTML tags. Instead of basic tags like <div> or <span>, you create meaningful tags like <GeneCard>, <SampleList>, <QcDashboard>. Just as labs break down complex procedures into protocol units for management, React breaks down UI into component units.
Components: Creating Custom Tags
Components are the core of React. One function = one tag:
function GeneCard() {
return (
<div>
<h3>TP53</h3>
<p>Chromosome: 17p13.1</p>
<p>Function: tumor suppressor</p>
</div>
);
}Once you create this function, you can use it as a <GeneCard /> tag. It must start with an uppercase letter โ React uses lowercase for regular HTML tags and uppercase for user-created components.
Combine multiple components to build the full screen:
function App() {
return (
<div>
<Header />
<GeneCard />
<SampleList />
</div>
);
}Looking at the App function alone, you can tell in one second that this screen consists of Header, GeneCard, and SampleList. Complex HTML is hidden behind meaningful names.
Three benefits of components:
- Readability: The code structure is visible at a glance
- Reusability: If you need
<GeneCard />in 10 places, just use it 10 times - Maintainability: To change the card design, modify the
GeneCardfunction in one place and all cards update simultaneously
Props: Passing Data to Components
The GeneCard above always shows TP53. To show other genes, use Props (short for Properties). Same concept as function parameters:
function GeneCard(props) {
return (
<div>
<h3>{props.name}</h3>
<p>Chromosome: {props.chromosome}</p>
<p>Function: {props.geneFunction}</p>
</div>
);
}
function App() {
return (
<div>
<GeneCard name="TP53" chromosome="17p13.1" geneFunction="tumor suppressor" />
<GeneCard name="BRCA1" chromosome="17q21.31" geneFunction="DNA repair" />
<GeneCard name="EGFR" chromosome="7p11.2" geneFunction="receptor tyrosine kinase" />
</div>
);
}In <GeneCard name="TP53" chromosome="17p13.1" />, name, chromosome, and geneFunction are Props. React bundles these values into one object and passes it as the function's first parameter (props).
In JSX, curly braces {} mean "this is JavaScript territory." Writing {props.name} outputs the name value from the props object. Writing props.name without braces would literally display the text "props.name" on screen.
In lab terms โ GeneCard is the protocol template, and Props are the samples you plug into it. Same protocol, but different results depending on the sample (data).
List Rendering: Arrays to Components
If you have 100 samples instead of 3, you can't write <GeneCard /> 100 times. Process array data with a loop:
function SampleList(props) {
const items = [];
for (let i = 0; i < props.samples.length; i++) {
const sample = props.samples[i];
items.push(
<li key={sample.id}>
{sample.id}: OD={sample.od} โ {sample.status}
</li>
);
}
return (
<ul>{items}</ul>
);
}
function App() {
const sampleData = [
{ id: "S001", od: 1.85, status: "pass" },
{ id: "S002", od: 0.42, status: "fail" },
{ id: "S003", od: 2.10, status: "pass" }
];
return (
<div>
<h2>Sample QC Results</h2>
<SampleList samples={sampleData} />
</div>
);
}A few notes:
key={sample.id}โ elements created by loops must have a uniquekey. React uses it to track which items changed. Like assigning a unique ID to each lab sample.samples={sampleData}โ when passing arrays as Props, wrap with curly braces. Strings use quotes, JavaScript values (arrays, numbers, variables) use curly braces.
State: Managing Changing Data
Props are values received from outside and can't be changed inside the component. Like not being allowed to modify experiment conditions written on a protocol mid-way. But experiment progress status keeps changing โ "pending" โ "in progress" โ "complete." State manages this internal status.
function ExperimentTracker() {
const [status, setStatus] = React.useState("Pending");
const [count, setCount] = React.useState(0);
function handleStart() {
setStatus("In Progress");
setCount(count + 1);
}
function handleComplete() {
setStatus("Complete");
}
return (
<div>
<p>Experiment status: {status}</p>
<p>Run count: {count}</p>
<button onClick={handleStart}>Start Experiment</button>
<button onClick={handleComplete}>Complete Experiment</button>
</div>
);
}React.useState("Pending") returns two things:
statusโ current value ("Pending")setStatusโ function to change the value
Calling setStatus("In Progress") changes status, and React automatically re-renders the screen. No need to manually manipulate DOM with innerHTML โ just change State and the screen updates itself. This is React's biggest advantage.
| Props | State | |
|---|---|---|
| Direction | Comes from outside | Managed internally |
| Modifiable | No (read-only) | Yes (setState) |
| Analogy | Experiment conditions on a protocol | Experiment progress status |
| Example | Gene name, chromosome location | "Pending" โ "In Progress" โ "Complete" |
Full Example: QC Dashboard
Combining components, Props, and State, you can build a simple QC dashboard:
function QcResult(props) {
const style = {
color: props.passed ? "green" : "red",
fontWeight: "bold"
};
return (
<tr>
<td>{props.sampleId}</td>
<td>{props.od}</td>
<td style={style}>{props.passed ? "PASS" : "FAIL"}</td>
</tr>
);
}
function QcDashboard() {
const samples = [
{ id: "S001", od: 1.85, passed: true },
{ id: "S002", od: 0.42, passed: false },
{ id: "S003", od: 2.10, passed: true },
{ id: "S004", od: 0.15, passed: false }
];
const passCount = samples.filter(function(s) { return s.passed; }).length;
return (
<div>
<h2>QC Dashboard</h2>
<p>Passed: {passCount} / {samples.length}</p>
<table>
<thead>
<tr><th>Sample ID</th><th>OD</th><th>Result</th></tr>
</thead>
<tbody>
{samples.map(function(s) {
return <QcResult key={s.id} sampleId={s.id} od={s.od} passed={s.passed} />;
})}
</tbody>
</table>
</div>
);
}QcResult shows results for a single sample, QcDashboard wraps the whole thing. Even if data grows to 100 items, the QcResult component doesn't need a single change โ just add more data.
Try It Yourself (Faded Example)
Fill in the blanks to complete a React component that displays gene information.
function GeneInfo() {return (<div><h3>{props.}</h3><p>Length: {.length_bp} bp</p></div>);}// Usage: <GeneInfo name="TP53" length_bp={19149} />
Common Errors & Solutions
Q: Component doesn't appear on screen
If the component name starts with lowercase, React treats it as a regular HTML tag. Change geneCard โ GeneCard โ capitalize the first letter.
Q: Each child in a list should have a unique "key" prop warning
Elements created by loops are missing a key prop. Assign a unique value (ID, sample number, etc.) from each array item as key. Using the index (i) as key is not recommended.
Q: Confused about when to use curly braces vs quotes
Strings use quotes: name="TP53". Numbers, arrays, variables, and other JavaScript values use curly braces: count={42}, data={myArray}. To execute JavaScript code inside JSX, wrap it in curly braces: {props.name}.
Q: Changed State directly but screen didn't update
Changing values directly like status = "Complete" prevents React from detecting the change. You must use the setter function like setStatus("Complete") for React to re-render the screen.