Add react questions (#4492)
* Add more questions * wip: add lazy, conditional questions * wip: Add RSC questions * wip: add component's lifecycle * wip: add dependency array question * wip: add comment and state * chore: add more questions * wip: add list question * wip: add directive questions * fix: conventions and examples * wip: add custom hook question * wip: add hydration question * wip: add error boundary example * wip: add strict mode question * wip: investigating slow react app * Update src/data/question-groups/react/react.md * Update src/data/question-groups/react/react.md --------- Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>pull/4500/head
parent
aa6d48b775
commit
edcf0e683d
12 changed files with 454 additions and 4 deletions
@ -0,0 +1,32 @@ |
|||||||
|
In React functional components, lifecycle-like behaviors are achieved using hooks: |
||||||
|
|
||||||
|
## Mounting and Unmounting |
||||||
|
|
||||||
|
Utilizing the useEffect hook with an empty dependency array ([]) ensures the hook runs after the component mounts to the DOM. |
||||||
|
|
||||||
|
```js |
||||||
|
useEffect(() => { |
||||||
|
// do something after component mounts |
||||||
|
return () => { |
||||||
|
// do something before component unmounts |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
``` |
||||||
|
|
||||||
|
The cleanup function returned within the useEffect callback offers a mechanism for handling tasks when the component is about to **unmount**. |
||||||
|
|
||||||
|
## Updates |
||||||
|
|
||||||
|
The useEffect hook, when invoked without a dependency array or with specific dependencies, executes after every render or when specified prop/state changes are detected. |
||||||
|
|
||||||
|
```js |
||||||
|
useEffect(() => { |
||||||
|
// do something after every render |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
```js |
||||||
|
useEffect(() => { |
||||||
|
// do something after specific prop/state changes |
||||||
|
}, [state1, state2]); |
||||||
|
``` |
@ -0,0 +1,51 @@ |
|||||||
|
**Custom hooks** are a mechanism for code reuse in React and allow you to extract component logic into reusable functions. Custom hooks can be used to share logic between components or to abstract away complex logic to make components more readable. |
||||||
|
|
||||||
|
Let's look at an example of a custom hook that return network status information: |
||||||
|
|
||||||
|
## Creating a Custom hook |
||||||
|
|
||||||
|
Custom hooks are named with the prefix `use` and can call other hooks if needed. They can also accept arguments and return values. |
||||||
|
|
||||||
|
```js |
||||||
|
import { useState, useEffect } from 'react'; |
||||||
|
|
||||||
|
function useNetworkStatus() { |
||||||
|
const [isOnline, setIsOnline] = useState(true); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
function handleOnline() { |
||||||
|
setIsOnline(true); |
||||||
|
} |
||||||
|
|
||||||
|
function handleOffline() { |
||||||
|
setIsOnline(false); |
||||||
|
} |
||||||
|
|
||||||
|
window.addEventListener('online', handleOnline); |
||||||
|
window.addEventListener('offline', handleOffline); |
||||||
|
|
||||||
|
return () => { |
||||||
|
window.removeEventListener('online', handleOnline); |
||||||
|
window.removeEventListener('offline', handleOffline); |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
|
||||||
|
return isOnline; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The custom hook above uses the `useState` and `useEffect` hooks to track the network status of the browser. It returns a boolean value that indicates whether the browser is online or offline. |
||||||
|
|
||||||
|
## Using a Custom hook |
||||||
|
|
||||||
|
```js |
||||||
|
function NetworkStatus() { |
||||||
|
const isOnline = useNetworkStatus(); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<p>You are {isOnline ? 'online' : 'offline'}.</p> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
``` |
@ -0,0 +1,37 @@ |
|||||||
|
Error boundaries are special React components that catch JavaScript errors during rendering, in lifecycle methods, and during the constructor of whole tree below them. They are used to handle errors gracefully by displaying a fallback UI and preventing the entire application from crashing due to unhandled errors. |
||||||
|
|
||||||
|
You can use [react-error-boundary](https://npm.im/react-error-boundary) package to create error boundaries in your application. It provides a `ErrorBoundary` component that you can wrap around any component that might throw an error. The `ErrorBoundary` component takes a `FallbackComponent` prop that is used to render a fallback UI when an error occurs. |
||||||
|
|
||||||
|
## Capturing Errors |
||||||
|
|
||||||
|
```js |
||||||
|
import { ErrorBoundary } from 'react-error-boundary'; |
||||||
|
import { FetchData } from './FetchData'; |
||||||
|
|
||||||
|
function ErrorFallback({ error, resetErrorBoundary }) { |
||||||
|
return ( |
||||||
|
<div role="alert"> |
||||||
|
<p>Something went wrong:</p> |
||||||
|
<pre>{error.message}</pre> |
||||||
|
<button onClick={resetErrorBoundary}>Try again</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export function App() { |
||||||
|
return ( |
||||||
|
<ErrorBoundary FallbackComponent={ErrorFallback}> |
||||||
|
<FetchData /> |
||||||
|
</ErrorBoundary> |
||||||
|
); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This `FetchData` component will throw an error when it is rendered, and the `ErrorBoundary` component will catch the error and display the `ErrorFallback` component. |
||||||
|
|
||||||
|
```js |
||||||
|
export function FetchData() { |
||||||
|
throw new Error('Error fetching data'); |
||||||
|
return <p>This will never render</p>; |
||||||
|
} |
||||||
|
``` |
@ -0,0 +1,9 @@ |
|||||||
|
The `flushSync` function in React is used to flush updates synchronously. It schedules updates to be performed inside a high-priority task, ensuring that the updates are executed immediately and synchronously before returning control to the caller. |
||||||
|
|
||||||
|
```js |
||||||
|
import { flushSync } from 'react-dom'; |
||||||
|
|
||||||
|
flushSync(callback); |
||||||
|
``` |
||||||
|
|
||||||
|
This is useful in situations where you need the DOM to be updated immediately, such as for measurements or to ensure synchronous rendering. However, excessive use of `flushSync` can lead to degraded performance, so it should be used judiciously. |
@ -0,0 +1,33 @@ |
|||||||
|
There are many reasons why an app might be slow. It could be due to a slow network, a slow backend, or a slow client. It could also be due to a memory leak, unnecessary re-renders, or large bundle sizes. |
||||||
|
|
||||||
|
Here are some tips to help you investigate and fix performance issues: |
||||||
|
|
||||||
|
## Use the React DevTools Profiler |
||||||
|
|
||||||
|
The React DevTools Profiler helps you visualize how components render and identify costly renderings. It can also help you identify unnecessary re-renders. |
||||||
|
|
||||||
|
## Check for Unnecessary Renders |
||||||
|
|
||||||
|
Ensure that components don't render more often than needed. Be clear about the `useEffect` dependencies and avoid creating new objects or arrays every render, as these can trigger unnecessary child component renders. Tools like [why-did-you-render](https://npm.im/@welldone-software/why-did-you-render) can help spot unnecessary re-renders. |
||||||
|
|
||||||
|
## Analyze Bundle Size |
||||||
|
|
||||||
|
Use your production build to analyze your bundle size. Tools like [webpack-bundle-analyzer](https://npm.im/webpack-bundle-analyzer) or [source-map-explorer](https://npm.im/source-map-explorer) can help you see if large libraries or unused code is slowing down the initial load. |
||||||
|
|
||||||
|
## Optimize Images & Assets |
||||||
|
|
||||||
|
Ensure images are appropriately sized and use modern formats. Also, consider using CDNs for assets that don't change often. |
||||||
|
|
||||||
|
## Lazy Load Components |
||||||
|
|
||||||
|
Use `lazy()` and dynamic imports to split your bundle and load components only when they're needed. This can help reduce the initial load time. |
||||||
|
|
||||||
|
## Check Network Requests |
||||||
|
|
||||||
|
Slow API calls or fetching large amounts of data can affect performance. Optimize your backend, paginate data, or cache results. You can also use tools like [@tanstack/react-query](https://npm.im/@tanstack/react-query) or [swr](https://npm.im/swr) to help manage data fetching and caching. |
||||||
|
|
||||||
|
## Use Production Build for Testing |
||||||
|
|
||||||
|
Ensure you're testing the performance on a production build, as development builds are often slower due to extra checks and logs. |
||||||
|
|
||||||
|
Regularly profiling and monitoring your app can help you spot and fix performance issues before they become significant problems. You can use tools like [Lighthouse](https://developers.google.com/web/tools/lighthouse) or [Calibre](https://calibreapp.com) to monitor your app's performance over time. |
@ -0,0 +1,39 @@ |
|||||||
|
You can use React's `lazy()` function in conjunction with dynamic `import()` to lazily load a component. This is often combined with `Suspense` to display fallback content while the component is being loaded. |
||||||
|
|
||||||
|
```js |
||||||
|
// The component has to be exported as a default export |
||||||
|
export default function RoadmapRender() { |
||||||
|
return <h1>This is a lazily-loaded component!</h1>; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
```js |
||||||
|
import { lazy, Suspense } from 'react'; |
||||||
|
|
||||||
|
const LazyRoadmapRender = lazy(() => delay(import('./RoadmapRender'))); |
||||||
|
|
||||||
|
export function App() { |
||||||
|
const [showRoadmapRender, setShowRoadmapRender] = useState(false); |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<button onClick={() => setShowRoadmapRender(true)}> |
||||||
|
Show RoadmapRender |
||||||
|
</button> |
||||||
|
{showRoadmapRender && ( |
||||||
|
<Suspense fallback={<div>Loading...</div>}> |
||||||
|
<LazyRoadmapRender /> |
||||||
|
</Suspense> |
||||||
|
)} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Helper function to simulate a 2 seconds delay |
||||||
|
function delay(promise) { |
||||||
|
return new Promise((resolve) => setTimeout(resolve, 2000)).then( |
||||||
|
() => promise |
||||||
|
); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The `RoadmapRender` component is lazily loaded and rendered inside the `Suspense` component. While the component is being loaded, the `Suspense` component will display the fallback content. |
@ -0,0 +1,17 @@ |
|||||||
|
In React, you can render a list by using the JavaScript `map` function to iterate over an array of items and return a JSX element for each item. It's important to provide a unique `key` prop to each element in the list for React's diffing algorithm to function efficiently during re-renders. Here's a basic example: |
||||||
|
|
||||||
|
```javascript |
||||||
|
const items = ['Apple', 'Banana', 'Cherry']; |
||||||
|
|
||||||
|
function FruitList() { |
||||||
|
return ( |
||||||
|
<ul> |
||||||
|
{items.map((fruit, index) => ( |
||||||
|
<li key={index}>{fruit}</li> |
||||||
|
))} |
||||||
|
</ul> |
||||||
|
); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
> Note: While using the index as a key can work in some cases, it's generally not recommended for dynamic lists where items can be added, removed, or reordered. |
@ -0,0 +1,19 @@ |
|||||||
|
Strict Mode is a tool in React for highlighting potential problems in an application. By wrapping a component tree with `StrictMode`, React will activate additional checks and warnings for its descendants. This doesn't affect the production build but provides insights during development. |
||||||
|
|
||||||
|
```js |
||||||
|
import { StrictMode } from 'react'; |
||||||
|
import { createRoot } from 'react-dom/client'; |
||||||
|
|
||||||
|
const root = createRoot(document.getElementById('root')); |
||||||
|
root.render( |
||||||
|
<StrictMode> |
||||||
|
<App /> |
||||||
|
</StrictMode> |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
In Strict Mode, React does a few extra things during development: |
||||||
|
|
||||||
|
1. It renders components twice to catch bugs caused by impure rendering. |
||||||
|
2. It runs side-effects (like data fetching) twice to find mistakes in them caused by missing effect cleanup. |
||||||
|
3. It checks if deprecated APIs are used, and logs a warning message to the console if so. |
@ -0,0 +1,23 @@ |
|||||||
|
Suspense is a component in React that lets you specify the fallback content to display while waiting for a component to load. It is used in conjunction with `lazy()` to lazily load components. |
||||||
|
|
||||||
|
```js |
||||||
|
import { lazy, Suspense } from 'react'; |
||||||
|
|
||||||
|
const LazyRoadmapRender = lazy(() => import('./RoadmapRender')); |
||||||
|
|
||||||
|
export function App() { |
||||||
|
const [show, setShow] = useState(false); |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<button onClick={() => setShow(true)}>Show</button> |
||||||
|
{show && ( |
||||||
|
<Suspense fallback={<div>Loading...</div>}> |
||||||
|
<LazyRoadmapRender /> |
||||||
|
</Suspense> |
||||||
|
)} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Until the `RoadmapRender` component is loaded, the `Suspense` component will display the `Loading...` fallback content. |
@ -0,0 +1,67 @@ |
|||||||
|
`useTransition` hook allows you to mark certain updates as **transitions** so they can be deprioritized, allowing other, more urgent updates to be processed first. This ensures that the UI remains responsive during updates that might take some time. |
||||||
|
|
||||||
|
```js |
||||||
|
import { useTransition, useState } from 'react'; |
||||||
|
import { Posts } from './Posts'; |
||||||
|
import { Home } from './Home'; |
||||||
|
import { Contact } from './Contact'; |
||||||
|
|
||||||
|
export function App() { |
||||||
|
const [isPending, startTransition] = useTransition(); |
||||||
|
const [page, setPage] = useState('home'); |
||||||
|
|
||||||
|
function changePage(newPage: string) { |
||||||
|
startTransition(() => { |
||||||
|
setPage(newPage); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<button onClick={() => changePage('home')}>Home</button> |
||||||
|
<button onClick={() => changePage('posts')}>Posts</button> |
||||||
|
<button onClick={() => changePage('contact')}>Contact</button> |
||||||
|
<hr /> |
||||||
|
{isPending && <div>Loading...</div>} |
||||||
|
{page === 'home' && <Home />} |
||||||
|
{page === 'posts' && <Posts />} |
||||||
|
{page === 'contact' && <Contact />} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
```js |
||||||
|
export function Home() { |
||||||
|
return <div>Home</div>; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
```js |
||||||
|
export function Contact() { |
||||||
|
return <div>Contact</div>; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Posts component is artificially delayed by 500ms to emulate extremely slow code. |
||||||
|
|
||||||
|
```js |
||||||
|
export function Posts() { |
||||||
|
const items = []; |
||||||
|
for (let i = 0; i < 500; i++) { |
||||||
|
items.push(<SlowPost key={i} />); |
||||||
|
} |
||||||
|
return <ul>{items}</ul>; |
||||||
|
} |
||||||
|
|
||||||
|
function SlowPost() { |
||||||
|
const startTime = performance.now(); |
||||||
|
while (performance.now() - startTime < 1) { |
||||||
|
// Do nothing for 1 ms per item to emulate extremely slow code |
||||||
|
} |
||||||
|
|
||||||
|
return <li>Post</li>; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Now when you click on the `Posts` button, you'll notice that the UI remains responsive and you can still switch to other pages while the posts are loading. Try removing the `startTransition` wrapper around `setPage` in `changePage` to see the difference. |
@ -0,0 +1,5 @@ |
|||||||
|
Virtual DOM works in this steps: |
||||||
|
|
||||||
|
1. Whenever any underlying data changes, new virtual DOM representation will be created. |
||||||
|
2. Then the difference between the previous DOM representation and the new one is calculated. |
||||||
|
3. Once the calculations are done, the real DOM will be updated with only the things that have actually changed. |
Loading…
Reference in new issue