React is a JavaScript library developed by Facebook for building user interfaces, especially for single-page applications. It’s used because it allows developers to create reusable components, making it easier to manage large applications. React also uses a virtual DOM for efficient updates, improving performance by only re-rendering components when necessary.
A component is a self-contained, reusable piece of code that returns HTML via JSX. Components allow you to break down the UI into manageable pieces, each with its logic. React has two main types of components: functional and class components.
function Greeting() {
return <h1>Hello, World!</h1>;
}
JSX, or JavaScript XML, is a syntax extension that looks similar to HTML and is used to define React components. JSX makes it easier to write and understand component structure by combining HTML with JavaScript.
const element = <h1>Hello, JSX!</h1>;
The virtual DOM is a lightweight copy of the actual DOM kept in memory. When changes occur, React updates the virtual DOM first, calculates the differences, and updates only the changed parts in the real DOM, improving performance.
Props (short for properties) are data passed from a parent component to a child component. They allow components to communicate with each other and enable reusability by making components customizable.
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
State is an object that holds data or information about the component. Unlike props, which are read-only, state is mutable and can change over time, allowing React to re-render components dynamically.
Class components are ES6 classes extending React.Component
, using lifecycle methods and this.state
. Functional components are simpler and rely on React hooks like useState
and useEffect
to manage state and lifecycle.
useState
is a hook that adds state to functional components. It returns an array with the current state value and a function to update that value.
const [count, setCount] = useState(0);
React events are similar to JavaScript events but use camelCase syntax. Event handlers are passed as function references rather than strings.
function handleClick() {
alert("Button clicked!");
}
<button onClick={handleClick}>Click me</button>
A controlled component has its form data managed by React state. Inputs are controlled components when their values are linked to state, enabling React to dictate the input’s behavior.
const [inputValue, setInputValue] = useState("");
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />;
An uncontrolled component relies on the DOM itself for state. Input values are accessed via ref
without linking them to React state.
const inputRef = useRef(null);
<input ref={inputRef} />;
React.Fragment
is a wrapper that lets you group multiple elements without adding extra nodes to the DOM. It helps avoid unnecessary <div>
s in the DOM tree.
<React.Fragment>
<h1>Hello</h1>
<p>This is a Fragment</p>
</React.Fragment>
Keys are unique identifiers for elements in a list. They help React identify which items have changed, been added, or removed, improving performance during re-renders.
const items = [1, 2, 3];
items.map(item => <li key={item}>{item}</li>);
Conditional rendering means displaying elements based on a condition. React allows using JavaScript’s conditional operators (e.g., if
, ternary operator) within JSX.
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please log in</h1>}
useEffect
is a hook for handling side effects like data fetching or DOM manipulation. It runs after render, with dependencies dictating when it should re-run.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
Lists in React are created by mapping over an array and returning a component or element for each item, ensuring that each list item has a unique key
.
const items = ["Item1", "Item2", "Item3"];
const listItems = items.map((item, index) => <li key={index}>{item}</li>);
Refs provide a way to access DOM elements or React elements directly. They’re created with React.createRef
or useRef
and are commonly used to access input elements or store mutable values without triggering re-renders.
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
<input ref={inputRef} />
React.StrictMode
is a wrapper that activates additional checks and warnings during development, helping catch potential issues in the code.
create-react-app
is a CLI tool to quickly set up a React application with default configurations for Webpack, Babel, and other development tools. It saves time by offering a preconfigured development environment with zero setup.
In a typical create-react-app
structure, index.js
is the entry point where the React app is mounted to the DOM. App.js
is the root component where the main application logic resides, which is then imported and rendered by index.js
.
The Context API is a way to pass data through the component tree without passing props down manually at every level. It’s useful for managing global state or passing data to deeply nested components.
const ThemeContext = React.createContext("light");
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<ThemeContext.Consumer>
{value => <div>Current theme: {value}</div>}
</ThemeContext.Consumer>
);
}
React provides useState
and useReducer
hooks for local component state. For global state management, options include Context API, Redux, or libraries like Zustand or Recoil.
useEffect
runs asynchronously after the render, suitable for side effects like data fetching. useLayoutEffect
runs synchronously after all DOM changes, used for DOM manipulations to avoid flicker.
useEffect(() => {
console.log("Runs after render");
});
useLayoutEffect(() => {
console.log("Runs before paint");
});
HOCs are functions that take a component and return a new component, used to add additional functionality to existing components without modifying them directly.
function withLogger(Component) {
return function(props) {
console.log("Component is rendered");
return <Component {...props} />;
};
}
useReducer
is more suitable for managing complex state with multiple actions, similar to a Redux reducer.
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
Prop drilling occurs when data is passed through many layers of components unnecessarily. It can be avoided using the Context API or state management libraries.
Example: Context API example from Question 1 can be used here to avoid drilling theme value.
Lazy loading loads components only when needed, while code splitting splits code into chunks, reducing initial bundle size and improving load times.
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);
}
React.memo
is a higher-order component that prevents unnecessary re-renders of functional components by memoizing them, thus improving performance.
const MemoizedComponent = React.memo(function MyComponent({ value }) {
console.log("Rendered");
return <div>{value}</div>;
});
Custom hooks are reusable functions that encapsulate logic using hooks, allowing for modular and cleaner code.
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(setData);
}, [url]);
return data;
}
Error boundaries catch errors in the component tree and prevent crashes. They are implemented as class components using componentDidCatch
and getDerivedStateFromError
.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error(error, info);
}
render() {
if (this.state.hasError) return <h1>Something went wrong.</h1>;
return this.props.children;
}
}
Reconciliation is the process of React comparing the virtual DOM with the real DOM to determine the most efficient way to update the UI by only applying necessary changes.
Side effects (e.g., data fetching) are managed using the useEffect
hook, where clean-up functions are added to avoid memory leaks.
Render props are patterns for sharing code between components by using a prop with a function that returns a component.
function DataFetcher({ render }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(setData);
}, []);
return render(data);
}
Optimization techniques include code splitting, lazy loading, memoization, avoiding inline functions, and efficient state management.
useRef
creates a reference to a DOM element or variable that persists across renders without causing re-renders.
const inputRef = useRef(null);
<input ref={inputRef} />;
Suspense
allows displaying fallback content while loading components or data asynchronously, useful with React.lazy
.
Define an async function inside useEffect
and call it, or use then
chaining.
useEffect(() => {
async function fetchData() {
const data = await fetch("api/data");
setData(data);
}
fetchData();
}, []);
Portals allow rendering children into a DOM node outside the component’s parent hierarchy, useful for modals or tooltips.
ReactDOM.createPortal(<ModalContent />, document.getElementById("modal-root"));
React’s diffing algorithm compares virtual DOM trees, updating only the changed nodes to optimize DOM updates.
useCallback
memoizes a function, preventing re-creation on every render, useful for functions passed as props to child components.
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
SSR renders React components on the server instead of the client, sending pre-rendered HTML to the browser, which improves initial load speed and SEO. Next.js is commonly used for SSR in React applications.
export async function getServerSideProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await res.json();
return { props: { data } };
}
Hydration is the process where React attaches event listeners to static HTML generated by SSR, making it interactive. After HTML is rendered on the server, React “hydrates” it on the client, allowing interactivity without a full re-render.
Concurrent rendering lets React interrupt rendering to focus on higher-priority updates, making the UI more responsive. With the useTransition
hook, non-urgent UI changes can be deferred.
const [isPending, startTransition] = useTransition();
Suspense boundaries allow React to “suspend” rendering of components until data or resources are ready. This is especially powerful with Concurrent Mode and the new use
hook for data fetching.
Custom hooks can use useMemo
to cache values based on dependencies, reducing redundant calculations and improving performance.
function useCachedData(data) {
return useMemo(() => processData(data), [data]);
}
Fibers are units of work that React uses to break down the render tree, allowing React to pause work and resume, optimizing performance with incremental rendering.
React.lazy
loads components asynchronously, useful for code-splitting. It uses import()
to fetch code only when needed, improving page load performance.
React.PureComponent
is a base class for components that only re-render when there’s a change in shallow props or state comparison. It’s useful for optimizing class components.
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.data}</div>;
}
}
The Profiler
API measures rendering performance of components, tracking render
times and identifying bottlenecks.
<Profiler id="MyComponent" onRender={handleRender}>
<MyComponent />
</Profiler>
The Context API is suited for simple, local state sharing. Redux is more powerful and suited for complex global state management, allowing for middleware, time-travel debugging, and centralized state.
useImperativeHandle
customizes the instance value exposed to parent components, used with forwardRef
for controlled access to a child’s methods.
const CustomInput = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}));
return <input ref={inputRef} />;
});
Module federation allows multiple independent apps to run together, each having its own build and bundle, promoting reusability and modularization in micro-frontends.
useTransition
defers non-urgent state updates, allowing React to prioritize urgent tasks, maintaining smooth user interactions during data-heavy updates.
useDeferredValue
delays a value until after high-priority updates, ideal for handling large, complex calculations triggered by user interactions.
const deferredValue = useDeferredValue(searchQuery);
Recoil is a React state management library that supports complex state sharing, atomized state for each component, and efficient UI updates without prop drilling.
Context updates can be optimized by splitting context, memoizing values, and using selectors to prevent unnecessary re-renders in large applications.
Dynamic imports, used with import()
, load modules on demand, useful for code-splitting. They reduce initial load time and improve performance by loading code when necessary.
Redux Saga uses generators to handle side effects like asynchronous calls in Redux, making it more powerful and suitable for complex asynchronous flows compared to Redux Thunk.
Memory leaks can be prevented by canceling async calls, clearing timers, using cleanup
functions in useEffect
, and avoiding excessive DOM elements.
useSyncExternalStore
subscribes to external data stores in a way compatible with Concurrent Mode, ensuring the store updates are consistent with React’s rendering system.