Temperature Converter

ReactEasy15 min

Prompt

Your task is to build a temperature converter in React.

The converter shows two inputs side by side, one labelled Celsius and one labelled Fahrenheit. When the user types a number into either input, the other input should update at the same time to show the matching temperature. The two values always stay in sync, so a user can convert in either direction without pressing a button.

When the user clears one input, the other should become empty as well.

Requirements

  • Show two labelled inputs, one for Celsius and one for Fahrenheit.
  • Typing a number in either input updates the other input live.
  • Clearing one input clears the other.
  • Apply basic styling so the converter is readable and easy to use.

Example

Temperature Converter

Playground

Hint 1

You will need to keep both numbers in state, one for Celsius and one for Fahrenheit. Keeping each input as its own piece of state lets you show the typed value in one field and the calculated value in the other.

Hint 2

When the user types in one input, update that input's state with what they typed, then calculate the other temperature from it and update the other input's state too. The two formulas are: Fahrenheit is celsius * 9 / 5 + 32, and Celsius is (fahrenheit - 32) * 5 / 9.

Hint 3

Think about what should happen when the input is empty. If you run an empty string through the calculation you will get NaN, which is not something we want to show the user. Before calculating, check if the field was cleared and, if so, clear the other field as well.

Solution

Explanation

This is an easy warm up question that interviewers use to check if you are comfortable with controlled inputs and keeping two pieces of state in sync.

The problem is to build two inputs, Celsius and Fahrenheit, that stay in sync. The user types a number in one and the other updates live to show the same temperature. There is no convert button, the conversion happens on every keystroke, and it works in both directions.

Now, let us start solving.

Reading the starter code

In the starter code we are given two inputs already on screen, one labelled Celsius and one labelled Fahrenheit. Right now they are plain inputs with no state behind them, so typing in one does nothing to the other. Our job is to wire them up. We will build this in three small pieces: a reusable input component, a TemperatureConverter component that holds the state and the conversion, and a thin App that just renders the converter.

Building the input component

We start with the smaller piece. Both inputs look and behave the same, so rather than write the markup twice we create one reusable component called TemperatureInput. It takes a label, the current value, and an onChange function.

components/TemperatureInput.js

import React from 'react';
export default function TemperatureInput({
label,
value,
onChange,
}) {
return (
<label className="field">
<span className="field-label">{label}</span>
<input
className="field-input"
type="number"
value={value}
onChange={(event) => onChange(event.target.value)}
/>
</label>
);
}

The component does not know anything about Celsius or Fahrenheit. It only knows how to show a labelled input and report back what the user typed. This keeps it reusable, both inputs are the same component with different props. Whatever renders it owns the values and decides what each change means.

Why a separate component

Some might say one input is too small to deserve its own component. But here we have two inputs that look and behave identically. Extracting one TemperatureInput means we write the markup once and use it twice. If we later want to add a unit symbol or validation styling, we change it in one place.

Building the converter component

The state and the conversion do not belong in App.js. We keep App.js thin and put them in their own component called TemperatureConverter. This keeps the entry file simple and gives the converter a clear home that is easy to find and reuse.

We give TemperatureConverter two pieces of state, one for each input, and render a TemperatureInput for each. We start both values as empty strings because the inputs are empty when the page loads.

components/TemperatureConverter.js

import React from 'react';
import TemperatureInput from './TemperatureInput';
export default function TemperatureConverter() {
const [celsius, setCelsius] = React.useState('');
const [fahrenheit, setFahrenheit] = React.useState('');
return (
<div className="converter">
<TemperatureInput
label="Celsius"
value={celsius}
onChange={setCelsius}
/>
<TemperatureInput
label="Fahrenheit"
value={fahrenheit}
onChange={setFahrenheit}
/>
</div>
);
}

We store the values as strings, not numbers. The value you read back from an input is always a string, even for a number input, and an empty input gives an empty string. If we stored numbers we would have to convert back and forth, and an empty field has no clean number to represent it.

Now App.js only has to render the converter. It sets up the page wrapper and drops TemperatureConverter inside it.

App.js

import React from 'react';
import './styles.css';
import TemperatureConverter from './components/TemperatureConverter';
export default function App() {
return (
<main className="wrapper">
<TemperatureConverter />
</main>
);
}

At this point both inputs work on their own, but they are not connected. Typing in Celsius updates only the Celsius value. Next we make a change in one update the other.

Converting in both directions

When the user types in the Celsius input, we need to do two things: store what they typed, and calculate the matching Fahrenheit value and store that too. We add a handler for each direction inside TemperatureConverter.

components/TemperatureConverter.js

import React from 'react';
import TemperatureInput from './TemperatureInput';
const roundToOneDecimal = (value) =>
String(Math.round(value * 10) / 10);
export default function TemperatureConverter() {
const [celsius, setCelsius] = React.useState('');
const [fahrenheit, setFahrenheit] = React.useState('');
const handleCelsiusChange = (value) => {
setCelsius(value);
if (value === '') {
setFahrenheit('');
return;
}
const celsiusValue = parseFloat(value);
setFahrenheit(
roundToOneDecimal((celsiusValue * 9) / 5 + 32)
);
};
const handleFahrenheitChange = (value) => {
setFahrenheit(value);
if (value === '') {
setCelsius('');
return;
}
const fahrenheitValue = parseFloat(value);
setCelsius(
roundToOneDecimal(((fahrenheitValue - 32) * 5) / 9)
);
};
return (
<div className="converter">
<TemperatureInput
label="Celsius"
value={celsius}
onChange={handleCelsiusChange}
/>
<TemperatureInput
label="Fahrenheit"
value={fahrenheit}
onChange={handleFahrenheitChange}
/>
</div>
);
}

Let us walk through handleCelsiusChange. First we store the raw string the user typed, so the Celsius input shows exactly what they entered. Then we check if the field is empty and, if it is, we clear Fahrenheit and stop. Otherwise we turn the string into a number with parseFloat, run the formula, and store the result in Fahrenheit. The Fahrenheit handler is the mirror image, using the reverse formula.

We round the calculated value to one decimal place with roundToOneDecimal. Without it, 36.6 Celsius would show as 97.88000000000001 Fahrenheit because of how floating point numbers work. We keep one decimal so it displays as 97.9 instead.

Common Pitfalls

  1. A common mistake is to keep only one piece of state and try to derive the second value during render. It feels cleaner, but it breaks two way editing, because you can no longer tell which field the user is typing in. Two inputs that can each be edited need two pieces of state.
  2. Candidates often forget the empty case. If you skip the empty check and feed an empty string to the formula, parseFloat('') gives NaN and the other input shows NaN to the user. Always clear the other field when the current field is empty.

Styling the converter

The styling is light. We stack the two inputs in a column, give them labels, and centre the whole thing on the page.

*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
width: 100%;
}
.wrapper {
padding: 40px 12px;
display: flex;
justify-content: center;
}
.converter {
display: flex;
flex-direction: column;
gap: 16px;
width: 280px;
}
.field {
display: flex;
flex-direction: column;
gap: 8px;
}
.field-label {
font-size: 14px;
font-weight: 600;
}
.field-input {
padding: 8px 12px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}

As with most interface questions, we start with box-sizing: border-box so padding and borders are included in the width and we do not get surprise overflow. The wrapper centres the converter using flexbox. The converter stacks the two fields in a column with a gap between them. Each field puts its label above its input. This is enough to make the converter clear and usable without spending much time on styling.

Communicate While You Code

Even on an easy question, talk through the decisions you make. Say why you are keeping two pieces of state instead of one, why you store the values as strings, and how you are handling the empty field. Interviewers want to hear your reasoning, not just see working code. A short, easy question is a good chance to show that you think about edge cases before you are asked about them.

Interviewer Criteria

HTML/CSS

  • Did I label both inputs clearly so the user knows which is Celsius and which is Fahrenheit?

  • Did I style the converter quickly and keep it readable?

  • Are my CSS class names self-explanatory and meaningful?

JavaScript

  • Did I get both conversion formulas right and in the correct direction?

  • Did I handle the empty input so the other field never shows NaN?

  • Did I round the calculated value so floating point noise does not reach the user?

React

  • Did I use two pieces of state so both inputs can be edited independently?

  • Are both inputs controlled, with their value coming from state?

  • Did I store the values as strings so an empty input is easy to represent?

Component Architecture

  • Did I keep App thin and put the state and conversion in a dedicated TemperatureConverter component?

  • Did I extract a reusable TemperatureInput component instead of duplicating the input markup?

  • Does TemperatureConverter own the state while each TemperatureInput only reports changes back up?

Time Checkpoints

  • 10:00 AM

    Interview starts 👥

  • 10:02 AM

    Prompt given by the interviewer

  • 10:04 AM

    Candidate reads the prompt and asks clarifying questions

  • 10:07 AM

    Two controlled inputs rendered with state

  • 10:11 AM

    Conversion wired up in both directions

  • 10:13 AM

    Empty input and rounding handled

  • 10:15 AM

    Styling completed

  • 10:18 AM

    Discussion with interviewer

  • 10:20 AM

    Interview ends ✅