visit
useState
and useEffect
, allow you to manage state and side effects in functional components, simplifying the development process.
JSX Transpilation: JSX is first transpiled (translated) into regular JavaScript using tools like Babel. During this process, JSX elements are converted into React.createElement()
calls.
const element = <h1>Hello, world!</h1>;
is transpiled to:
const element = React.createElement('h1', null, 'Hello, world!');
function MyComponent() {
return (
<div>
<h1>Hello, React!</h1>
</div>
);
}
export default MyComponent;
Class components are ES6 classes that extend React.Component
. They can also hold and manage state and lifecycle methods.
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return (
<div>
<h1>Hello, React!</h1>
</div>
);
}
}
export default MyComponent;
useState
and useEffect
.
<ChildComponent name="Luis" />
, where name
is a prop.setState
or useState
.const [count, setCount] = useState(0)
.
children
prop in React?The children
prop in React is a special prop that allows components to pass other elements or components as its content. It is used to display whatever is placed between the opening and closing tags of a component.
defaultProps
in React?defaultProps
in React is a way to define default values for props in a component. If a prop is not provided by the parent component, the component will use the value specified in defaultProps
.
You can create elements in a loop in React by using JavaScript’s array methods, such as map()
.
useState
hook in React?The most used hook in React is the useState()
hook. It allows functional components to manipulate DOM elements before each render. Using this hook we can declare a state variable inside a function but only one state variable can be declared using a single useState()
hook. Whenever the useState()
hook is used, the value of the state variable is changed and the new variable is stored in a new cell in the stack.
useEffect()
hook and how to manage side effects?The useEffect()
hook in React eliminates the side effect of using class based components. It is used as an alternative to componentDidUpdate()
method. The useEffect()
hook accepts two arguments where second argument is optional.
import styles from './App.module.css';
react-redux
?
react-redux
?
react-redux
.A single state tree makes it easier to debug or inspect an application
It gives you a faster development cycle by enabling you to persist in your app's navigation state
This feature ensures that no events like network callbacks or views can change the state. They can only express an intent to change the state
Actions are just plain objects; they can be logged, serialized, stored, and later replayed for debugging or testing purposes
import React, { useState } from 'react';
function ButtonExample() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // Update the state when the button is clicked
};
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click me</button> {/* onClick event */}
</div>
);
}
export default ButtonExample;
function ButtonExample() {
const handleClick = (name) => {
console.log(Hello, ${name});
};
return (
<button onClick={() => handleClick('Luis')}>Say Hello</button>
);
}
function ButtonExample() {
const handleSubmit = (event) => {
event.preventDefault(); // Prevent form submission
console.log('Form was submitted');
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
function MultipleButtons() {
const handleButtonClick = (action) => {
console.log(${action} button clicked);
};
return (
<div>
<button onClick={() => handleButtonClick('Save')}>Save</button>
<button onClick={() => handleButtonClick('Delete')}>Delete</button>
</div>
);
}
In React, inputs are handled using controlled components, where the input's value is tied to the component's state through the value
attribute, and updated using the onChange
event. This allows for efficient management of inputs like text fields, checkboxes, and dropdowns.
const [inputValue, setInputValue] = useState('');
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
1. BrowserRouter
import { BrowserRouter, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Route path="/" component={Home} />
</BrowserRouter>
);
}
2. Routes
and Route
Routes
is a wrapper that allows multiple routes to be defined.Route
defines a path and the component that should render when the URL matches that path.import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
3. Link
<a>
) tags to ensure React Router can manage the navigation.import { Link } from 'react-router-dom';
function Navigation() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
);
}
4. useNavigate
import { useNavigate } from 'react-router-dom';
function Form() {
const navigate = useNavigate();
const handleSubmit = () => {
// After form submission, navigate to another page
navigate('/success');
};
return <button onClick={handleSubmit}>Submit</button>;
}
5. useParams
import { useParams } from 'react-router-dom';
function Product() {
const { id } = useParams(); // Extract 'id' from URL
return <div>Product ID: {id}</div>;
}
6. useLocation
import { useLocation } from 'react-router-dom';
function CurrentLocation() {
const location = useLocation();
return <div>Current Path: {location.pathname}</div>;
}
7. Navigate
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated }) {
return isAuthenticated ? <Dashboard /> : <Navigate to="/login" />;
}
8. Outlet
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<header>Header</header>
<Outlet /> {/* Renders child routes */}
<footer>Footer</footer>
</div>
);
}
Data fetching in React can be implemented using various methods, depending on your needs and the complexity of the application. The most common approach is to use React’s lifecycle methods or hooks like useEffect
to fetch data from APIs or other sources.
1. Using useEffect
Hook
The useEffect
hook allows you to perform side effects in functional components, including data fetching. This is the most common approach when working with functional components in React.
import React, { useState, useEffect } from 'react';
function DataFetchingComponent() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Fetch data when the component mounts
fetch('//api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => {
setData(data);
setLoading(false); // Data is fetched, stop loading
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []); // Empty dependency array ensures it only runs once when component mounts
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default DataFetchingComponent;
2. Using axios
for Data Fetching
Instead of using the native fetch
API, you can use third-party libraries like Axios, which makes handling promises simpler and supports features like request cancellation.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function AxiosDataFetching() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Fetch data using Axios
axios.get('//api.example.com/data')
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default AxiosDataFetching;
3. Using async/await
for Cleaner Syntax
You can use async/await
inside the useEffect
hook for more readable asynchronous code.
import React, { useState, useEffect } from 'react';
function AsyncDataFetching() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('//api.example.com/data');
if (!response.ok) {
throw new Error('Error fetching data');
}
const data = await response.json();
setData(data);
setLoading(false);
} catch (error) {
setError(error.message);
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default AsyncDataFetching;
import React from 'react';
import PropTypes from 'prop-types';
import './Button.css'; // Optional: for styling
const Button = ({ onClick, type, children, className, disabled }) => {
return (
<button
onClick={onClick}
type={type}
className={`btn ${className}`}
disabled={disabled}
>
{children}
</button>
);
};
Button.propTypes = {
onClick: PropTypes.func,
type: PropTypes.oneOf(['button', 'submit', 'reset']),
children: PropTypes.node.isRequired,
className: PropTypes.string,
disabled: PropTypes.bool,
};
Button.defaultProps = {
onClick: () => {},
type: 'button',
className: '',
disabled: false,
};
export default Button;
.btn {
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #ddd;
}
.btn:disabled {
cursor: not-allowed;
opacity: 0.5;
}
import React from 'react';
import Button from './Button';
function App() {
const handleClick = () => {
alert('Button clicked!');
};
return (
<div>
<Button onClick={handleClick} className="primary-button">
Click Me
</Button>
<Button onClick={handleClick} type="submit" disabled>
Submit
</Button>
</div>
);
}
export default App;
import React, { useState } from 'react';
const Form = () => {
const [formData, setFormData] = useState({
email: '',
password: '',
username: ''
});
const [errors, setErrors] = useState({
email: '',
password: '',
username: ''
});
const [isSubmitted, setIsSubmitted] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
const validate = () => {
const { email, password, username } = formData;
const newErrors = {};
if (!email) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(email)) {
newErrors.email = 'Email address is invalid';
}
if (!password) {
newErrors.password = 'Password is required';
} else if (password.length < 6) {
newErrors.password = 'Password must be at least 6 characters long';
}
if (!username) {
newErrors.username = 'Username is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
setIsSubmitted(true);
// Handle form submission (e.g., API call)
console.log('Form data submitted:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <p className="error">{errors.email}</p>}
</div>
<div>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <p className="error">{errors.username}</p>}
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <p className="error">{errors.password}</p>}
</div>
<button type="submit">Submit</button>
{isSubmitted && <p className="success">Form submitted successfully!</p>}
</form>
);
};
export default Form;
.error {
color: red;
font-size: 0.875em;
}
.success {
color: green;
font-size: 1em;
}
form {
max-width: 400px;
margin: auto;
}
div {
margin-bottom: 15px;
}
import React from 'react';
import Form from './Form';
import './Form.css';
function App() {
return (
<div className="App">
<h1>Registration Form</h1>
<Form />
</div>
);
}
export default App;
Controlled components in React have their form data managed by the component’s state, with updates handled via onChange
events. Uncontrolled components manage their form data through the DOM, using refs to access values. Controlled components offer better control and synchronization, while uncontrolled components are simpler but less flexible.
useState
and useRef
in React?useState
: Creates state variables that trigger re-renders when updated.
useRef
: Creates a mutable reference that persists across renders without causing re-renders, commonly used to access DOM elements directly.
StrictMode
in React?StrictMode
is a tool for highlighting potential problems in a React application. It activates additional checks and warnings for its descendants, such as identifying unsafe lifecycle methods, detecting side effects in components, and ensuring best practices. StrictMode
is a development-only feature and has no impact on production builds.
Custom hooks in React are functions that start with use
and allow you to encapsulate and reuse stateful logic across multiple components. They can call other hooks and return values such as state variables or functions. For example, a custom hook like useCounter
can manage counter state and provide methods to increment, decrement, or reset the counter.
Code splitting in React is a technique to improve performance by breaking up the application into smaller chunks that are loaded on demand. This is achieved using React.lazy
and Suspense
, which dynamically import components and show a fallback UI while they are being loaded.
Redux Toolkit is a library that simplifies using Redux by providing tools like createSlice
for reducing boilerplate code, configureStore
for setting up the store, and built-in support for handling asynchronous logic. It streamlines state management in Redux and improves the development experience.
useCallback
hook in React?The useCallback
hook is used to memoize a function, preventing it from being recreated on every render unless its dependencies change. This is useful for optimizing performance, especially when passing functions to child components.
useMemo
hook in React?The useMemo
hook in React is used to memoize expensive calculations and prevent unnecessary re-computation. It recalculates the result only when its dependencies change, improving performance by avoiding re-renders of components with heavy computations.
React.memo
and how is it used for performance optimization?React.memo
is a higher-order component (HOC) in React that optimizes performance by preventing unnecessary re-renders of a component. It works by memoizing the rendered output of a functional component and only re-renders it when its props change.
In React's Context API, the Provider supplies data to components, while the Consumer accesses that data. The Provider
wraps components and passes a value
prop, and the Consumer
retrieves that value. In functional components, the useContext
hook is often used instead of Consumer
for easier access to context.
React.memo()
or pureComponent
: Prevent unnecessary re-renders of components that haven't changed.lazy
and Suspense
.useCallback
and useMemo
: Optimize expensive calculations and function references in functional components.
lazy
function and the Suspense
component.To implement lazy loading in React, use the React.lazy
function to dynamically import components and the Suspense
component to handle the loading state while the component is being loaded.
Use React.lazy
to load the component dynamically:
const LazyComponent = React.lazy(() => import('./LazyComponent'));
Wrap the lazy-loaded component with Suspense
, providing a fallback UI (e.g., a loader) while the component is being fetched:
import React, { Suspense } from 'react';
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
export default App;
This ensures that LazyComponent
is only loaded when needed, optimizing performance.
// sum.js
export function sum(a, b) {
return a + b;
}
// sum.test.js
import { sum } from './sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
npm run test
import React, { useState, useEffect } from 'react';
const Timer = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// Set up an interval
const intervalId = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
// Cleanup function to clear the interval on component unmount
return () => {
clearInterval(intervalId);
};
}, []); // Empty dependency array means the effect runs once on mount
return (
<div>
<h1>Timer: {count}</h1>
</div>
);
};
export default Timer;
import React, { useState, useCallback } from 'react';
// Debounce function
const debounce = (func, delay) => {
let timeout;
return (...args) => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func(...args);
}, delay);
};
};
// Throttle function
const throttle = (func, delay) => {
let lastCall = 0;
return (...args) => {
const now = new Date().getTime();
if (now - lastCall < delay) return;
lastCall = now;
func(...args);
};
};
const DebounceThrottleInputs = () => {
const [debouncedValue, setDebouncedValue] = useState('');
const [throttledValue, setThrottledValue] = useState('');
// Handle debounced input
const handleDebouncedInput = useCallback(
debounce((value) => {
setDebouncedValue(value);
}, 1000),
[]
);
// Handle throttled input
const handleThrottledInput = useCallback(
throttle((value) => {
setThrottledValue(value);
}, 1000),
[]
);
return (
<div>
<div>
<label>Debounced Input (1s delay): </label>
<input
type="text"
onChange={(e) => handleDebouncedInput(e.target.value)}
/>
<p>Debounced Value: {debouncedValue}</p>
</div>
<div>
<label>Throttled Input (1s delay): </label>
<input
type="text"
onChange={(e) => handleThrottledInput(e.target.value)}
/>
<p>Throttled Value: {throttledValue}</p>
</div>
</div>
);
};
export default DebounceThrottleInputs;
Debounce: The debounce
function delays invoking the input handler until after a set delay (1 second here) has passed since the last event.
Throttle: The throttle
function limits how often the input handler can be invoked (at most once per second).
React.memo()
: Wrap functional components with React.memo
to prevent unnecessary re-renders if the props don’t change.useMemo
and useCallback
: Use useMemo
to memoize expensive calculations and useCallback
to memoize functions, preventing them from being recreated on every render.React.lazy
and Suspense
: Use lazy loading to split your code and load components only when needed, reducing initial load time.shouldComponentUpdate
or PureComponent
for class components.react-window
) for large lists to render only visible items in the DOM.NODE_ENV=production
) to take advantage of React’s optimizations.
useMemo
, and useCallback
, and when is it appropriate to use each?React.memo
: Prevents re-renders of a component if its props haven’t changed.useMemo
: Memoizes a value to avoid recalculating it unless its dependencies change.useCallback
: Memoizes a function to prevent its recreation on every render unless its dependencies change.Use React.memo
for components, useMemo
for expensive calculations, and useCallback
for stable functions passed to child components.
Action: An object that describes what happened in the application, containing a type
and optional payload
.
setState
or state updater functions.// Bad
this.state.value = newValue;
// Good
this.setState({ value: newValue });
// Bad
this.setState({ derivedValue: this.state.value * 2 });
// Good
const derivedValue = value * 2;
React.memo
for functional components and shouldComponentUpdate
for class components.// Bad
const MyComponent = ({ onClick }) => <button onClick={onClick}>Click</button>;
// Good
const MyComponent = React.memo(({ onClick }) => <button onClick={onClick}>Click</button>);
key
prop for elements in lists.// Bad
{items.map(item => <div>{item}</div>)}
// Good
{items.map(item => <div key={item.id}>{item}</div>)}
// Bad
const ComplexComponent = () => { /* large logic */ };
// Good
const PartOne = () => { /* logic */ };
const PartTwo = () => { /* logic */ };
const ComplexComponent = () => <><PartOne /><PartTwo /></>;
useCallback
.// Bad
<button onClick={() => doSomething()}>Click</button>;
// Good
const handleClick = () => doSomething();
<button onClick={handleClick}>Click</button>;
class ErrorBoundary extends React.Component {
// Implement error handling logic
}
8. Over-fetching Data
useLayoutEffect
and useEffect
. When would you prefer one over the other?useEffect
:
useEffect
will execute after the browser has painted the UI.
useLayoutEffect
:
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
function Example() {
const [height, setHeight] = useState(0);
const divRef = useRef(null);
// useLayoutEffect: Measure DOM before painting
useLayoutEffect(() => {
const measuredHeight = divRef.current.getBoundingClientRect().height;
setHeight(measuredHeight);
}, []);
// useEffect: Fetch data after render
useEffect(() => {
// Fetch some data here
console.log("Data fetched after render");
}, []);
return (
<div ref={divRef} style={{ height: '100px' }}>
The div height is: {height}
</div>
);
}
export default Example;
useLayoutEffect
ensures that the height of the div
is measured before the browser paints the UI.useEffect
handles side effects that don’t need to block rendering, like fetching data.
Using React.lazy()
and Suspense
:
Normally, React.lazy loads components on demand. You can combine lazy loading with preloading to ensure components are loaded ahead of time (e.g., when hovering over a button).
const LazyComponent = React.lazy(() => import('./MyComponent'));
// Preload the component when hovering over a button
function preloadComponent() {
import('./MyComponent');
}
function App() {
return (
<div>
<button onMouseEnter={preloadComponent}>
Preload Component
</button>
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
</div>
);
}
React Router with Route Preloading:
Combine React.lazy()
with dynamic imports to preload route components that are likely to be visited soon.
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
// Preload the 'About' component
function preloadAbout() {
import('./About');
}
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about" onMouseEnter={preloadAbout}>
About (Preload)
</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
</Suspense>
</Router>
);
}
Using React Query
for Prefetching:
Libraries like React Query
allow you to prefetch data before it’s needed, improving UX by ensuring data is already available when the component is rendered.
import { useQuery, QueryClient } from 'react-query';
const queryClient = new QueryClient();
// Fetch data function
const fetchUserData = async () => {
const res = await fetch('/api/user');
return res.json();
};
// Component that uses data
function UserComponent() {
const { data, isLoading } = useQuery('user', fetchUserData);
if (isLoading) return <div>Loading...</div>;
return <div>User: {data.name}</div>;
}
// Preload data
queryClient.prefetchQuery('user', fetchUserData);
Preloading images, fonts, or other assets ensures smoother transitions when components are displayed. You can use <link rel="preload">
in the HTML or JavaScript APIs to preload assets.
function preloadImage(src) {
const img = new Image();
img.src = src;
}
function ImageComponent() {
const imageUrl = '//example.com/image.jpg';
preloadImage(imageUrl); // Preload the image
return <img src={imageUrl} alt="example" />;
}
5. Prefetching with rel="prefetch"
:
Modern browsers support prefetching of resources (JS, CSS, etc.). You can add rel="prefetch"
links to preload components that the user might need later.
<link rel="prefetch" href="/static/js/component.js" />
React.lazy
with prefetching (via import()
) to load components in advance.onMouseEnter
events and dynamic imports.React Query
to prefetch data before it is needed in a component.<link rel="prefetch">
.
React.memo
: Wrap the component with React.memo
to prevent unnecessary re-renders if the props haven’t changed.useCallback
and useMemo
: Memoize functions and computed values to avoid recalculations or re-creating objects on every render.react-window
) to only render visible items in large lists.
React.memo
to prevent unnecessary re-renders of components when props haven’t changed. Optimize component structures and reduce frequent state updates.React.lazy
and Suspense
) to load parts of the app only when needed.react-window
) to render only the visible portion of large datasets.useCallback
to memoize callback functions to avoid creating new functions on every render.<link rel="preload">
for critical assets.
useState
or event handlers) to group multiple state updates into a single render cycle.import React, { createContext, useContext, useState, useMemo } from 'react';
// Create context
const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
const [state, setState] = useState({
user: null,
theme: 'light',
// other state...
});
const value = useMemo(() => ({
user: state.user,
theme: state.theme,
setUser: (user) => setState((prev) => ({ ...prev, user })),
setTheme: (theme) => setState((prev) => ({ ...prev, theme })),
}), [state.user, state.theme]); // Only update when these change
return (
<GlobalContext.Provider value={value}>
{children}
</GlobalContext.Provider>
);
};
// Usage in a component
const UserProfile = () => {
const { user, setUser } = useContext(GlobalContext);
return (
<div>
<h1>{user ? user.name : 'Guest'}</h1>
<button onClick={() => setUser({ name: 'John Doe' })}>Login</button>
</div>
);
};
In React, error boundaries are used to catch JavaScript errors in components and provide a fallback UI instead of crashing the entire application. You can implement error boundaries by creating a class component that implements the componentDidCatch
lifecycle method and the getDerivedStateFromError
static method.
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render shows the fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log the error to an error reporting service
console.error("Error logged:", error, errorInfo);
}
render() {
if (this.state.hasError) {
// Fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Wrap components that might throw errors with the ErrorBoundary
:
function App() {
return (
<ErrorBoundary>
<SomeComponent />
</ErrorBoundary>
);
}
React.memo
, useMemo
, and useCallback
).
useEffect
or useState
with Jest and React Testing Library?To test components that use hooks like useEffect
or useState
with Jest and React Testing Library, you need to focus on testing how the component behaves when the state changes or side effects are triggered, rather than testing the implementation details of the hooks themselves. The idea is to ensure that your component works as expected with the hooks.
1. Testing useState
:
For components that use useState
, the goal is to test how the component's state changes based on user interactions or other events.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
Test for useState
with Jest and React Testing Library:
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments count when button is clicked', () => {
render(<Counter />);
const button = screen.getByText(/Increment/i);
const countText = screen.getByText(/Count: 0/i);
// Ensure the initial state is rendered
expect(countText).toBeInTheDocument();
// Simulate a button click
fireEvent.click(button);
// Expect the count to increment
expect(screen.getByText(/Count: 1/i)).toBeInTheDocument();
});
We render the Counter
component.
Use fireEvent.click()
to simulate a user interaction that triggers a state change.
2. Testing useEffect
:
For components using useEffect
, you can test how the component behaves after the effect is triggered (e.g., fetching data, subscribing to events).
import React, { useEffect, useState } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
}
fetchData();
}, []);
if (!data) {
return <div>Loading...</div>;
}
return <div>Data: {data.name}</div>;
}
export default DataFetcher;
Test for useEffect
with Jest and React Testing Library:
To test asynchronous operations like fetching data, you’ll use mocking with Jest. You can mock the fetch
API and control its return values to simulate different scenarios (like success or failure).
import { render, screen, waitFor } from '@testing-library/react';
import DataFetcher from './DataFetcher';
// Mock the fetch API
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: 'Test Data' }),
})
);
test('renders fetched data after loading', async () => {
render(<DataFetcher />);
// Initially, "Loading..." text should appear
expect(screen.getByText(/Loading/i)).toBeInTheDocument();
// Wait for the data to load
await waitFor(() => screen.getByText(/Data: Test Data/i));
// Check if the data is rendered
expect(screen.getByText(/Data: Test Data/i)).toBeInTheDocument();
});
fetch
: We mock the fetch
function to return test data without making real network requests.waitFor
: Since fetching data is asynchronous, we use waitFor
to wait until the component updates and displays the fetched data.
You can use @testing-library/react-hooks
for testing custom hooks.
import { useState, useEffect } from 'react';
function useFetchData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const result = await response.json();
setData(result);
setLoading(false);
}
fetchData();
}, [url]);
return { data, loading };
}
export default useFetchData;
import { renderHook } from '@testing-library/react-hooks';
import useFetchData from './useFetchData';
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: 'Test Data' }),
})
);
test('should fetch and return data', async () => {
const { result, waitForNextUpdate } = renderHook(() => useFetchData('/api/data'));
// Initially, loading should be true
expect(result.current.loading).toBe(true);
// Wait for the hook to finish the data fetching
await waitForNextUpdate();
// After the update, data should be present and loading should be false
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual({ name: 'Test Data' });
});
renderHook
.waitForNextUpdate
is used to wait for the asynchronous fetch operation to complete before asserting the data.
useState
: Simulate user interactions that trigger state changes, and check that the component’s output updates correctly.useEffect
: Mock external dependencies (e.g., fetch
) and verify the component behavior once the effect has completed.waitFor
or similar methods to handle async updates in the UI.renderHook
to test the logic of hooks independently from component.Description:
Create a custom hook called useForm
to manage complex forms with validation and state management. The hook should:
Requirements:
Solution:
import { useState } from 'react';
// Custom hook for form management
function useForm(initialState, validate) {
const [values, setValues] = useState(initialState);
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
if (validate[name]) {
const error = validate[name](value);
setErrors({
...errors,
[name]: error,
});
}
};
const handleReset = () => {
setValues(initialState);
setErrors({});
};
return {
values,
errors,
handleChange,
handleReset,
};
}
// Validation rules
const validate = {
name: (value) => (value ? '' : 'Name is required'),
email: (value) =>
/\S+@\S+\.\S+/.test(value) ? '' : 'Invalid email address',
password: (value) =>
value.length >= 6 ? '' : 'Password must be at least 6 characters',
};
// Form Component using useForm
function Form() {
const initialState = { name: '', email: '', password: '' };
const { values, errors, handleChange, handleReset } = useForm(
initialState,
validate
);
return (
<form>
<div>
<label>Name:</label>
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
/>
{errors.name && <span>{errors.name}</span>}
</div>
<div>
<label>Email:</label>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
/>
{errors.email && <span>{errors.email}</span>}
</div>
<div>
<label>Password:</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
/>
{errors.password && <span>{errors.password}</span>}
</div>
<button type="button" onClick={handleReset}>
Reset Form
</button>
</form>
);
}
export default Form;
Description: You have a list of 10,000 items that need to be rendered on a page. Implement a component that optimizes the rendering of this list using "list virtualization" techniques. The component should load only the visible items in the viewport and render additional elements as the user scrolls.
Requirements:
react-window
or implement a custom solution.
Solution using react-window
:
npm install react-window
import React from 'react';
import { FixedSizeList as List } from 'react-window';
// Sample data: Array of 10,000 items
const items = Array.from({ length: 10000 }, (_, index) => `Item ${index + 1}`);
function Row({ index, style }) {
return (
<div style=/templates/ className="list-item">
{items[index]}
</div>
);
}
function VirtualizedList() {
return (
<List
height={400} // Height of the viewport
itemCount={items.length} // Total number of items
itemSize={35} // Height of each item
width={300} // Width of the list
>
{Row}
</List>
);
}
export default VirtualizedList;
useForm
hook to manage form state and validation. It accepts an initial state and validation rules, handling updates and form resets.react-window
:
We used the react-window
library to handle list virtualization for a large list of 10,000 items.