종종 동일한 데이터를 여러 컴포넌트에서 사용될 경우가 있다. 이럴 때 가까운 조상 컴포넌트로로 state를 옮기는게 좋다.

물의 끓는 여부를 추정하는 온도 계산기 컴포넌트를 만들자.

function BoilingVerdict(props) {
	if(props.celsius >= 100) {
		return <p>The water would boil.</p>
	}
	return <p>The water would not boil.</p>
}

그 다음은 Calculator라는 컴포넌트를 만들자. 이 컴포넌트는 input 태그를 통해 온도를 입력하고 그 값을 this.state.temperature에 저장한다.

class Calculator extends React.Component {
	constructor(props) {
		super(props);
		this.state = {temperature: ''};

		this.handleChange = this.handleChange.bind(this);
	}

	handleChange(event) {
		this.setState({temperature: event.target.value});
	}

	render() {
		const temperature = this.state.temperature;
		return (
			<fieldset>
				<legend>Enter temperature in Celsius:</legend>
				<input value={temperature} onChange={this.handleChange} />
				<BoilingVerdict celsius={parseFloat(temperature)}/>
			</fieldset>
		);
	}
}

새로운 요청사항으로 섭씨뿐만 아니라 화씨도 입력하도록 했다.

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.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 {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

이렇게 분리하였지만 문제가 있다. 한 곳에 온도를 입력하더래도 다른 곳의 온도가 변하지 않기 때문이다.

변환 함수 작성하기

먼저, 섭씨를 화씨로, 또는 그 반대로 변환해주는 함수를 작성한다.

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();
}

state 끌어 올리기

이제 state를 끌어올려 섭씨와 화씨를 동기화 해야 한다. 그러려면 TemperatureInput의 값을 끌어올려 Calculator 컴포넌트로 옮기면 된다. 또한, state 변경이 외부에서 일어나므로, onChange를 이르킬 이벤트를 Calculator 컴포넌트에게 같이 받아야한다. 코드로 다음과 같다.

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>
    );
  }
}

Calculator 컴포넌트

class Calculator extends React.Component {
	constructor(props){
		super(props);
		this.state = {temperature: '', scale: 'c'};
		this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
	}

	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 (
			<TemperatureInput
				scale="c"
				temperature={celsius}
				onTemperatureChange={this.handleCelsiusChange} />
			<TemperatureInput
				scale="f"
				temperature={fahrenheit}
				onTemperatureChange={this.handleFahrenheitChange} />
			<BoilingVerdict
          celsius={parseFloat(celsius)} />
		);
	}
}

교훈

변경이 일어나는 데이터에 대해서 그 원천은 하나만 둬야 한다. 그러기 위해서 state를 끌어올리는 작업을 하게 된다.