State promotion
In React applications, any variable data should have only one corresponding and unique "data source". Usually, state is first added to the component that needs to render data. Then, if other components also need this state, it can be promoted to the common parent component of these components
For example, you want to realize an example: you want the data in the Celsius temperature input box and the Fahrenheit input box to be changed synchronously, and when the Celsius temperature is greater than 100, you will give a prompt that the water will boil, otherwise the water will not boil
show you the code
In this example, since the Celsius temperature input box and the Fahrenheit input box need to be synchronized, move the state shared by the two components up to their nearest common parent component. Since the props of the two TemperatureInput components are from the common parent component Calculator, the contents of the two input boxes will always be consistent. At the same time, if some data can be derived from props or state, it should not exist in state, such as celsiusValue and fahrenheitValue in this example
function Calculator(){ const [temperature, setTemperature] = useState<string>('') const [scale, setScale] = useState<string>('c') const toCelsius = useCallback((fahrenheit)=>{ return (fahrenheit - 32) * 5 / 9; },[]) const toFahrenheit = useCallback((celsius)=>{ return (celsius * 9 / 5) + 32; },[]) const tryConvert = useCallback((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 getRightTemperature = useCallback(()=>{ const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature; return { celsius, fahrenheit, } },[temperature,scale]) const handleCTempChange = useCallback((val)=>{ setScale('c') setTemperature(val) },[]) const handleFTempChange = useCallback((val)=>{ setScale('f') setTemperature(val) },[]) return( <> <TemperatureInput scale='c' temperature={getRightTemperature().celsius} onTempChange={handleCTempChange}/> <TemperatureInput scale='f' temperature={getRightTemperature().fahrenheit} onTempChange={handleFTempChange}/> <BoilingVerdict celsius={parseFloat(getRightTemperature().celsius))}/> </> ) } interface ITprops{ scale: 'c' | 'f', temperature:string, onTempChange:(val:string)=>void } function TemperatureInput(props:ITprops){ const scaleNames = { c: 'Celsius', f: 'Fahrenheit' }; return( <fieldset> <legend>Enter temperature in {scaleNames[props.scale]}:</legend> <input value={props.temperature} onChange={(e)=>props.onTempChange(e.target.value)}></input> </fieldset> ) } interface IBprops{ celsius:number } function BoilingVerdict(props:IBprops){ if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; }
React Philosophy
Composition VS inheritance: in react, there is no need to use inheritance to build component hierarchy. Props and composition provide a flexible way to clearly and safely customize the appearance and behavior of components. Components can accept any props, including basic data types, react elements and functions. If you want to reuse non UI functions among components, It can be extracted into a separate JS module
How to build an application:
Divide component hierarchy (single function principle) - > create a static page (top-down or bottom-up) - > determine the minimum (and complete) representation of state (dry) - > determine the location of state (whether state promotion is required) - > Add reverse data flow (callback function transfer)
DRY: Don't Repeat Yourself. Only the minimum set of variable state s required by the application is reserved, and other data are generated by their calculation.