I attempted to refactor a React class component (fahrenheit/celcius temperature converter) to functional components with hooks (in NextJS) but these fields are not updating state as expected when numbers are added to either TemperatureInput
field. Nor is BoilingVerdict
updating when triggered.
I've tried many variations to the change handles but can't find what I'm missing.
Could someone please point me on the right track?
Thanks in advance!
Original class component:
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) 32;
}
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Calculator />);
Same component converted to functional components/hooks (but not working):
import React, { useState } from "react";
const scaleNames = {
c: "Celsius",
f: "Fahrenheit",
};
const toCelsius = (fahrenheit) => {
return ((fahrenheit - 32) * 5) / 9;
};
const toFahrenheit = (celsius) => {
return (celsius * 9) / 5 32;
};
const tryConvert = (temperature, convert) => {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return "";
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
};
const BoilingVerdict = (props) => {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
};
const TemperatureInput = (props) => {
const handleChange = () => {
props.onTemperatureChange();
};
const temperature = props.temperature;
const scale = props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={handleChange} />
</fieldset>
);
};
export default function Temp(props) {
const [state, setState] = useState({ temperature: "", scale: "c" });
const handleCelsiusChange = (temperature) => {
setState({ scale: "c", temperature });
};
const handleFahrenheitChange = (temperature) => {
setState({ scale: "f", temperature });
}
const scale = state.scale;
const temperature = state.temperature;
const celsius =
scale === "f"
? tryConvert(temperature, toCelsius())
: temperature;
const fahrenheit =
scale === "c"
? tryConvert(temperature, toFahrenheit())
: temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={handleCelsiusChange}
/>
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={handleFahrenheitChange}
/>
<BoilingVerdict celsius={parseFloat(celsius)} />
</div>
);
}
The original is from the React Docs site example on Lifting State, pen here: https://codepen.io/gaearon/pen/WZpxpz?editors=0010
Thanks again!
CodePudding user response:
You had two little errors in your code:
- input onChange emits an event. You can get the actual value that was put in by accessing e.target.value.
Without this change, the temperature is and stays undefined, since handleChange in your handleTemperatureChange-Handlers in Temp are called without any parameters.
const TemperatureInput = (props) => {
const handleChange = (e) => {
props.onTemperatureChange(e.target.value);
};
const temperature = props.temperature;
const scale = props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={(e) => handleChange(e)} />
</fieldset>
);
};
Your calls to tryConvert were incorrect.
tryConvert(temperature, toCelsius())
In this case the second parameter is not a function but its return value. If you want to feed a function to tryConvert add only the function name:
tryConvert(temperature, toCelsius)
Putting this together the code works fine
import React, { useState } from "react";
const scaleNames = {
c: "Celsius",
f: "Fahrenheit"
};
const toCelsius = (fahrenheit) => {
return ((fahrenheit - 32) * 5) / 9;
};
const toFahrenheit = (celsius) => {
return (celsius * 9) / 5 32;
};
const tryConvert = (temperature, convert) => {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return "";
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
};
const BoilingVerdict = (props) => {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
};
const TemperatureInput = (props) => {
const handleChange = (e) => {
props.onTemperatureChange(e.target.value);
};
const temperature = props.temperature;
const scale = props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={(e) => handleChange(e)} />
</fieldset>
);
};
export default function Temp(props) {
const [state, setState] = useState({ temperature: "", scale: "c" });
const handleCelsiusChange = (temperature) => {
console.log(temperature)
setState({ scale: "c", temperature });
};
const handleFahrenheitChange = (temperature) => {
setState({ scale: "f", temperature });
}
const scale = state.scale;
const temperature = state.temperature;
const celsius =
scale === "f"
? tryConvert(temperature, toCelsius())
: temperature;
const fahrenheit =
scale === "c"
? tryConvert(temperature, toFahrenheit)
: temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={handleCelsiusChange}
/>
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={handleFahrenheitChange}
/>
<BoilingVerdict celsius={parseFloat(celsius)} />
</div>
);
}