As developers diving into the world of React, we quickly realize that while building components is simple, building smart, efficient, and testable applications takes a deeper understanding. In this blog post, we’ll walk through some fundamental React concepts — Conditional Rendering, Lifting State Up, and Testing — that help us write better and more maintainable code.
Conditional Rendering in React
In React, conditional rendering lets us dynamically change what’s displayed on the screen based on certain conditions — just like if
statements in JavaScript.
Let’s say we have a component that shows different messages based on whether a user is logged in.
function Greeting(props) { const isLoggedIn = props.isLoggedIn; return ( <div> {isLoggedIn ? <h2>Welcome back!</h2> : <h2>Please sign in.</h2>} </div> ); }
We can also use other methods, like early returns, &&
short-circuiting, or even switch statements, depending on our use case.
function Notification(props) { return ( <div> {props.show && <p>You have new notifications!</p>} </div> ); }
Lifting State Up
Sometimes, we need to share state between two sibling components. Instead of duplicating state, we “lift” the state up to their common parent component.
Here’s a classic example: a temperature converter that synchronizes Celsius and Fahrenheit inputs.
Step 1: Two child components
function CelsiusInput({ temperature, onTemperatureChange }) { return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={e => onTemperatureChange(e.target.value)} /> </fieldset> ); } function FahrenheitInput({ temperature, onTemperatureChange }) { return ( <fieldset> <legend>Enter temperature in Fahrenheit:</legend> <input value={temperature} onChange={e => onTemperatureChange(e.target.value)} /> </fieldset> ); }
Step 2: Lift state up to the parent
import React, { useState } from 'react'; function toCelsius(fahrenheit) { return ((fahrenheit - 32) * 5) / 9; } function toFahrenheit(celsius) { return (celsius * 9) / 5 + 32; } function TemperatureCalculator() { const [temperature, setTemperature] = useState(''); const [scale, setScale] = useState('c'); const handleCelsiusChange = (temp) => { setScale('c'); setTemperature(temp); }; const handleFahrenheitChange = (temp) => { setScale('f'); setTemperature(temp); }; const celsius = scale === 'f' ? toCelsius(temperature) : temperature; const fahrenheit = scale === 'c' ? toFahrenheit(temperature) : temperature; return ( <div> <CelsiusInput temperature={celsius} onTemperatureChange={handleCelsiusChange} /> <FahrenheitInput temperature={fahrenheit} onTemperatureChange={handleFahrenheitChange} /> </div> ); } export default TemperatureCalculator;
Testing in React
As our apps grow, testing becomes essential. We can use Jest (shipped with Create React App) and React Testing Library to write simple and effective tests.
Component
function Greeting({ isLoggedIn }) { return ( <div> {isLoggedIn ? <h2>Welcome back!</h2> : <h2>Please sign in.</h2>} </div> ); } export default Greeting;
Test File: Greeting.test.js
import { render, screen } from '@testing-library/react'; import Greeting from './Greeting'; test('renders welcome message when logged in', () => { render(<Greeting isLoggedIn={true} />); const message = screen.getByText(/welcome back/i); expect(message).toBeInTheDocument(); }); test('renders sign-in message when not logged in', () => { render(<Greeting isLoggedIn={false} />); const message = screen.getByText(/please sign in/i); expect(message).toBeInTheDocument(); });
Conclusion
React gives us powerful tools to build flexible, user-friendly interfaces. By understanding and using techniques like conditional rendering, lifting state up, and writing proper tests, we make our components not only smarter but also easier to debug and extend.
Leave a Reply