├── .gitignore
├── images
├── flux.png
├── logo.jpeg
├── phases.png
├── state.jpg
├── vdom1.png
├── vdom2.png
├── vdom3.png
├── devtoolsTab.png
├── phases16.3.jpg
├── phases16.4.png
├── error_boundary.png
├── collab
│ ├── BFCM2025.gif
│ ├── resumeloom.png
│ ├── greatfrontend-react.gif
│ └── greatfrontend-banner4x.png
└── devtoolsInspect.png
├── coding-exercise
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── exercises
│ │ ├── __template__
│ │ │ ├── index.js
│ │ │ ├── Problem.js
│ │ │ ├── README.md
│ │ │ └── Solution.js
│ │ ├── exercise-01-state-batching
│ │ │ ├── index.js
│ │ │ ├── Problem.js
│ │ │ └── Solution.js
│ │ ├── exercise-04-custom-hooks
│ │ │ ├── index.js
│ │ │ ├── Problem.js
│ │ │ └── Solution.js
│ │ ├── exercise-02-useeffect-dependencies
│ │ │ ├── index.js
│ │ │ ├── Problem.js
│ │ │ └── Solution.js
│ │ ├── exercise-03-useCallback-memoization
│ │ │ ├── index.js
│ │ │ ├── Problem.js
│ │ │ └── Solution.js
│ │ └── index.js
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── App.css
│ ├── components
│ │ ├── NavigationButton.js
│ │ ├── HomePage.js
│ │ └── ExerciseCard.js
│ ├── App.js
│ ├── logo.svg
│ └── serviceWorker.js
├── .gitignore
├── package.json
├── QUICK_START.md
├── ARCHITECTURE.md
└── README.md
├── .github
└── FUNDING.yml
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | # Cruft
2 | .DS_Store
3 | npm-debug.log
4 | .idea
--------------------------------------------------------------------------------
/images/flux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/flux.png
--------------------------------------------------------------------------------
/images/logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/logo.jpeg
--------------------------------------------------------------------------------
/images/phases.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/phases.png
--------------------------------------------------------------------------------
/images/state.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/state.jpg
--------------------------------------------------------------------------------
/images/vdom1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/vdom1.png
--------------------------------------------------------------------------------
/images/vdom2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/vdom2.png
--------------------------------------------------------------------------------
/images/vdom3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/vdom3.png
--------------------------------------------------------------------------------
/coding-exercise/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/images/devtoolsTab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/devtoolsTab.png
--------------------------------------------------------------------------------
/images/phases16.3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/phases16.3.jpg
--------------------------------------------------------------------------------
/images/phases16.4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/phases16.4.png
--------------------------------------------------------------------------------
/images/error_boundary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/error_boundary.png
--------------------------------------------------------------------------------
/images/collab/BFCM2025.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/collab/BFCM2025.gif
--------------------------------------------------------------------------------
/images/collab/resumeloom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/collab/resumeloom.png
--------------------------------------------------------------------------------
/images/devtoolsInspect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/devtoolsInspect.png
--------------------------------------------------------------------------------
/coding-exercise/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/coding-exercise/public/favicon.ico
--------------------------------------------------------------------------------
/coding-exercise/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/coding-exercise/public/logo192.png
--------------------------------------------------------------------------------
/coding-exercise/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/coding-exercise/public/logo512.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [sudheerj]
4 | custom: https://buymeacoffee.com/sudheerj
5 |
6 |
--------------------------------------------------------------------------------
/images/collab/greatfrontend-react.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/collab/greatfrontend-react.gif
--------------------------------------------------------------------------------
/images/collab/greatfrontend-banner4x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudheerj/reactjs-interview-questions/HEAD/images/collab/greatfrontend-banner4x.png
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/__template__/index.js:
--------------------------------------------------------------------------------
1 | export { default as Problem } from './Problem';
2 | export { default as Solution } from './Solution';
3 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-01-state-batching/index.js:
--------------------------------------------------------------------------------
1 | export { default as Problem } from './Problem';
2 | export { default as Solution } from './Solution';
3 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-04-custom-hooks/index.js:
--------------------------------------------------------------------------------
1 | export { default as Problem } from './Problem';
2 | export { default as Solution } from './Solution';
3 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-02-useeffect-dependencies/index.js:
--------------------------------------------------------------------------------
1 | export { default as Problem } from './Problem';
2 | export { default as Solution } from './Solution';
3 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-03-useCallback-memoization/index.js:
--------------------------------------------------------------------------------
1 | export { default as Problem } from './Problem';
2 | export { default as Solution } from './Solution';
3 |
--------------------------------------------------------------------------------
/coding-exercise/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/coding-exercise/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render( );
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/coding-exercise/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/coding-exercise/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/coding-exercise/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/coding-exercise/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/coding-exercise/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/coding-exercise/src/components/NavigationButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function NavigationButton({ onClick, children, style = {} }) {
4 | return (
5 |
24 | {children}
25 |
26 | );
27 | }
28 |
29 | export default NavigationButton;
30 |
--------------------------------------------------------------------------------
/coding-exercise/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "coding-exercise",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "react": "^16.13.1",
10 | "react-dom": "^16.13.1",
11 | "react-scripts": "3.4.1"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test",
17 | "eject": "react-scripts eject"
18 | },
19 | "eslintConfig": {
20 | "extends": "react-app"
21 | },
22 | "browserslist": {
23 | "production": [
24 | ">0.2%",
25 | "not dead",
26 | "not op_mini all"
27 | ],
28 | "development": [
29 | "last 1 chrome version",
30 | "last 1 firefox version",
31 | "last 1 safari version"
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-Present Sudheer Jonna
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/coding-exercise/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import './App.css';
3 | import HomePage from './components/HomePage';
4 | import NavigationButton from './components/NavigationButton';
5 | import { exercises } from './exercises';
6 |
7 | function App() {
8 | const [currentView, setCurrentView] = useState('home');
9 |
10 | const renderView = () => {
11 | if (currentView === 'home') {
12 | return ;
13 | }
14 |
15 | // Parse the view to get exercise ID and type (problem/solution)
16 | const [exerciseId, viewType] = currentView.split('-').reduce((acc, part, idx, arr) => {
17 | if (idx === arr.length - 1) {
18 | return [acc[0], part]; // Last part is the view type
19 | }
20 | return [acc[0] ? `${acc[0]}-${part}` : part, acc[1]];
21 | }, ['', '']);
22 |
23 | const exercise = exercises.find((ex) => ex.id === exerciseId);
24 |
25 | if (!exercise) {
26 | return
Exercise not found
;
27 | }
28 |
29 | const Component = viewType === 'solution' ? exercise.Solution : exercise.Problem;
30 | return ;
31 | };
32 |
33 | return (
34 |
35 | {currentView !== 'home' && (
36 | setCurrentView('home')}>
37 | ← Back to Home
38 |
39 | )}
40 | {renderView()}
41 |
42 | );
43 | }
44 |
45 | export default App;
46 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-01-state-batching/Problem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 1: State Batching and Event Handlers
5 | *
6 | * PROBLEM:
7 | * What will be the output after clicking the "Increment" button once?
8 | *
9 | * Options:
10 | * A) Counter: 3, Alert shows: 3
11 | * B) Counter: 3, Alert shows: 0
12 | * C) Counter: 1, Alert shows: 0
13 | * D) Counter: 1, Alert shows: 1
14 | *
15 | * BONUS: How would you modify this to make the counter increment by 3?
16 | */
17 |
18 | function Problem() {
19 | const [counter, setCounter] = useState(0);
20 |
21 | const handleIncrement = () => {
22 | setCounter(counter + 1);
23 | setCounter(counter + 1);
24 | setCounter(counter + 1);
25 | alert(`Counter value: ${counter}`);
26 | };
27 |
28 | return (
29 |
30 |
Exercise 1: State Batching Problem
31 |
Current Counter: {counter}
32 |
Increment
33 |
34 |
Question: What will happen when you click the button?
35 |
Think about:
36 |
37 | What value will the counter display?
38 | What value will the alert show?
39 | Why does this behavior occur?
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | export default Problem;
47 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/__template__/Problem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE X: [Exercise Title]
5 | *
6 | * PROBLEM:
7 | * [Describe the problem here]
8 | *
9 | * Options:
10 | * A) [Option A]
11 | * B) [Option B]
12 | * C) [Option C]
13 | * D) [Option D]
14 | *
15 | * BONUS: [Optional bonus question]
16 | */
17 |
18 | function Problem() {
19 | // Add your state and logic here
20 | const [state, setState] = useState(0);
21 |
22 | return (
23 |
24 |
Exercise X: [Exercise Title]
25 |
26 | {/* Your interactive component here */}
27 |
State: {state}
28 |
setState(state + 1)}>Click Me
29 |
30 | {/* Problem description */}
31 |
38 |
⚠️ Question: What will happen when you interact with this component?
39 |
Think about:
40 |
41 | Question 1?
42 | Question 2?
43 | Question 3?
44 |
45 |
46 | Tip: Open the browser console to see logs
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | export default Problem;
54 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-02-useeffect-dependencies/Problem.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 2: useEffect Dependencies and Infinite Loops
5 | *
6 | * PROBLEM:
7 | * What will happen when this component renders?
8 | *
9 | * Options:
10 | * A) The count increments once to 1 and stops
11 | * B) The count increments infinitely causing an infinite loop
12 | * C) The component throws an error
13 | * D) The count stays at 0
14 | *
15 | * BONUS: How would you fix this to increment only once on mount?
16 | */
17 |
18 | function Problem() {
19 | const [count, setCount] = useState(0);
20 | const [data, setData] = useState({ value: 0 });
21 |
22 | useEffect(() => {
23 | console.log('Effect running...');
24 | setCount(count + 1);
25 | setData({ value: count });
26 | }, [data]);
27 |
28 | return (
29 |
30 |
Exercise 2: useEffect Dependencies Problem
31 |
Count: {count}
32 |
Data Value: {data.value}
33 |
34 |
⚠️ Warning: This component has a bug!
35 |
Question: What will happen when this component renders?
36 |
37 | Will it render once?
38 | Will it cause an infinite loop?
39 | Why does this behavior occur?
40 | How would you fix it?
41 |
42 |
43 | Note: Check the browser console to see the effect running
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | export default Problem;
51 |
--------------------------------------------------------------------------------
/coding-exercise/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/coding-exercise/src/components/HomePage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ExerciseCard from './ExerciseCard';
3 | import { exercises } from '../exercises';
4 |
5 | function HomePage({ onNavigate }) {
6 | return (
7 |
8 |
React Coding Exercises
9 |
10 | Interactive coding exercises to test your React knowledge
11 |
12 |
13 |
14 | {exercises.map((exercise) => (
15 | onNavigate(`${exercise.id}-problem`)}
19 | onViewSolution={() => onNavigate(`${exercise.id}-solution`)}
20 | />
21 | ))}
22 |
23 |
24 |
25 |
💡 Tips for Success
26 |
27 | Try to solve the problem before viewing the solution
28 | Open the browser console to see effect logs
29 | Experiment with the code to deepen your understanding
30 | Read the explanations carefully to understand the "why"
31 |
32 |
33 |
34 |
35 |
📝 Contributing
36 |
To add a new exercise:
37 |
38 | Create a folder: exercises/exercise-XX-name/
39 | Add Problem.js and Solution.js
40 | Create index.js to export both components
41 | Register in exercises/index.js
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | export default HomePage;
49 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/index.js:
--------------------------------------------------------------------------------
1 | import * as Exercise01 from './exercise-01-state-batching';
2 | import * as Exercise02 from './exercise-02-useeffect-dependencies';
3 | import * as Exercise03 from './exercise-03-useCallback-memoization';
4 | import * as Exercise04 from './exercise-04-custom-hooks';
5 |
6 | /**
7 | * Exercise Registry
8 | *
9 | * To add a new exercise:
10 | * 1. Create a new folder: exercises/exercise-XX-name/
11 | * 2. Add Problem.js and Solution.js files
12 | * 3. Create index.js to export both components
13 | * 4. Import and add to the exercises array below
14 | */
15 |
16 | export const exercises = [
17 | {
18 | id: 'exercise-01',
19 | title: 'State Batching & Closures',
20 | description: 'Learn about React state updates, closures, and functional updates',
21 | difficulty: 'Medium',
22 | topics: ['useState', 'Closures', 'State Batching', 'Event Handlers'],
23 | Problem: Exercise01.Problem,
24 | Solution: Exercise01.Solution,
25 | },
26 | {
27 | id: 'exercise-02',
28 | title: 'useEffect Dependencies',
29 | description: 'Understand useEffect dependencies and avoid infinite loops',
30 | difficulty: 'Medium',
31 | topics: ['useEffect', 'Dependencies', 'Infinite Loops', 'useRef'],
32 | Problem: Exercise02.Problem,
33 | Solution: Exercise02.Solution,
34 | },
35 | {
36 | id: 'exercise-03',
37 | title: 'useCallback & Memoization',
38 | description: 'Prevent unnecessary re-renders with useCallback and React.memo',
39 | difficulty: 'Medium',
40 | topics: ['useCallback', 'React.memo', 'Performance', 'Re-renders'],
41 | Problem: Exercise03.Problem,
42 | Solution: Exercise03.Solution,
43 | },
44 | {
45 | id: 'exercise-04',
46 | title: 'Custom Hooks',
47 | description: 'Extract reusable logic with custom hooks for cleaner code',
48 | difficulty: 'Medium',
49 | topics: ['Custom Hooks', 'Code Reusability', 'DRY', 'Best Practices'],
50 | Problem: Exercise04.Problem,
51 | Solution: Exercise04.Solution,
52 | },
53 | // Add more exercises here...
54 | ];
55 |
56 | export default exercises;
57 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/__template__/README.md:
--------------------------------------------------------------------------------
1 | # Exercise Template
2 |
3 | This is a template folder for creating new exercises. **DO NOT** register this template in the exercise registry.
4 |
5 | ## How to Use This Template
6 |
7 | 1. **Copy this folder** and rename it:
8 | ```
9 | cp -r __template__ exercise-XX-your-topic-name
10 | ```
11 |
12 | 2. **Update Problem.js**:
13 | - Replace `[Exercise Title]` with your exercise title
14 | - Add your problem code and logic
15 | - Update the questions and options
16 |
17 | 3. **Update Solution.js**:
18 | - Replace `[Exercise Title]` with your exercise title
19 | - Add your solution code with explanations
20 | - Update all placeholder text
21 |
22 | 4. **Register in exercises/index.js**:
23 | ```javascript
24 | import * as ExerciseXX from './exercise-XX-your-topic-name';
25 |
26 | export const exercises = [
27 | // ... existing exercises
28 | {
29 | id: 'exercise-XX',
30 | title: 'Your Topic Name',
31 | description: 'Brief description',
32 | difficulty: 'Medium',
33 | topics: ['Topic1', 'Topic2'],
34 | Problem: ExerciseXX.Problem,
35 | Solution: ExerciseXX.Solution,
36 | },
37 | ];
38 | ```
39 |
40 | 5. **Test your exercise**:
41 | ```bash
42 | npm start
43 | ```
44 |
45 | ## Template Sections
46 |
47 | ### Problem.js
48 | - Exercise metadata (title, options, bonus)
49 | - Interactive component
50 | - Problem description with questions
51 |
52 | ### Solution.js
53 | - Answer and explanation
54 | - Problem demonstration (wrong approach)
55 | - Solution demonstration (correct approach)
56 | - Key takeaways
57 | - Code comparison
58 | - Common mistakes
59 |
60 | ## Styling Reference
61 |
62 | - Problem warning: `#fff3cd` with `#ffc107` border
63 | - Wrong approach: `#ffe6e6` background
64 | - Correct solution: `#e6ffe6` background
65 | - Information: `#e6f3ff` background
66 | - Key takeaways: `#f0f0f0` background
67 |
68 | ## Tips
69 |
70 | - Keep code examples concise and focused
71 | - Use emojis for visual hierarchy (❌ ✅ 💡 📚 ⚠️)
72 | - Add console.log statements for debugging
73 | - Test all interactive elements
74 | - Ensure responsive design with inline styles
75 |
--------------------------------------------------------------------------------
/coding-exercise/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-03-useCallback-memoization/Problem.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 3: useCallback and Unnecessary Re-renders
5 | *
6 | * PROBLEM:
7 | * In the code below, the ChildComponent re-renders every time the parent's count changes,
8 | * even though the child only depends on the handleClick function.
9 | *
10 | * Options:
11 | * A) ChildComponent re-renders only when button is clicked
12 | * B) ChildComponent re-renders every time count changes
13 | * C) ChildComponent never re-renders
14 | * D) React throws an error about missing dependencies
15 | *
16 | * BONUS: How would you prevent unnecessary re-renders of ChildComponent?
17 | */
18 |
19 | // Child component that should only re-render when its props change
20 | const ChildComponent = ({ onClick }) => {
21 | console.log('ChildComponent rendered');
22 | return (
23 |
24 |
I'm a child component
25 |
Click me from child
26 |
27 | );
28 | };
29 |
30 | function Problem() {
31 | const [count, setCount] = useState(0);
32 | const [childClicks, setChildClicks] = useState(0);
33 |
34 | // This function is recreated on every render
35 | const handleClick = () => {
36 | setChildClicks(prev => prev + 1);
37 | };
38 |
39 | return (
40 |
41 |
Exercise 3: useCallback & Memoization Problem
42 |
43 |
44 |
Parent Count: {count}
45 |
setCount(count + 1)}>Increment Parent Count
46 |
47 |
48 |
49 |
Child Clicks: {childClicks}
50 |
51 |
52 |
53 |
54 |
61 |
⚠️ Question: What happens when you click "Increment Parent Count"?
62 |
Think about:
63 |
64 | Does the ChildComponent re-render?
65 | Why does it re-render (or not)?
66 | How can you prevent unnecessary re-renders?
67 | Check the console to see render logs
68 |
69 |
70 | Tip: Open the browser console and click the parent button multiple times
71 |
72 |
73 |
74 | );
75 | }
76 |
77 | export default Problem;
78 |
--------------------------------------------------------------------------------
/coding-exercise/src/components/ExerciseCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const difficultyColors = {
4 | Easy: '#28a745',
5 | Medium: '#ffc107',
6 | Hard: '#dc3545',
7 | };
8 |
9 | function ExerciseCard({ exercise, onViewProblem, onViewSolution }) {
10 | return (
11 |
20 |
21 |
22 |
{exercise.title}
23 |
{exercise.description}
24 |
25 |
26 |
38 | {exercise.difficulty}
39 |
40 | {exercise.topics.map((topic) => (
41 |
54 | {topic}
55 |
56 | ))}
57 |
58 |
59 |
60 |
61 |
62 |
74 | View Problem
75 |
76 |
89 | View Solution
90 |
91 |
92 |
93 | );
94 | }
95 |
96 | export default ExerciseCard;
97 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/__template__/Solution.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE X: [Exercise Title] - SOLUTION
5 | *
6 | * ANSWER: [Correct option]
7 | *
8 | * EXPLANATION:
9 | *
10 | * 1. [First key point]
11 | * - [Detail]
12 | * - [Detail]
13 | *
14 | * 2. [Second key point]
15 | * - [Detail]
16 | * - [Detail]
17 | *
18 | * 3. [Third key point]
19 | * - [Detail]
20 | */
21 |
22 | function Solution() {
23 | // Example 1: The Problem
24 | const [problemState, setProblemState] = useState(0);
25 |
26 | // Example 2: The Solution
27 | const [solutionState, setSolutionState] = useState(0);
28 |
29 | return (
30 |
31 |
Exercise X: [Exercise Title] - Solution
32 |
33 | {/* The Problem Section */}
34 |
40 |
❌ The Problem
41 |
State: {problemState}
42 |
setProblemState(problemState + 1)}>
43 | Problematic Approach
44 |
45 |
51 | {`// Problematic code
52 | const [state, setState] = useState(0);
53 | // ... problematic implementation`}
54 |
55 |
56 | Why it's wrong: [Explanation]
57 |
58 |
59 |
60 | {/* The Solution Section */}
61 |
67 |
✅ The Solution
68 |
State: {solutionState}
69 |
setSolutionState(prev => prev + 1)}>
70 | Correct Approach
71 |
72 |
78 | {`// Correct code
79 | const [state, setState] = useState(0);
80 | // ... correct implementation`}
81 |
82 |
83 | Why it works: [Explanation]
84 |
85 |
86 |
87 | {/* Key Takeaways Section */}
88 |
93 |
📚 Key Takeaways
94 |
95 | Point 1: Explanation
96 | Point 2: Explanation
97 | Point 3: Explanation
98 | Point 4: Explanation
99 |
100 |
101 |
102 | {/* Code Comparison Section */}
103 |
109 |
💡 Code Comparison
110 |
116 | {`// ❌ WRONG
117 | // Wrong code example
118 |
119 | // ✅ CORRECT
120 | // Correct code example`}
121 |
122 |
123 |
124 | {/* Common Mistakes Section */}
125 |
131 |
⚠️ Common Mistakes
132 |
133 | ❌ Mistake 1
134 | ❌ Mistake 2
135 | ✅ Best practice 1
136 | ✅ Best practice 2
137 |
138 |
139 |
140 | );
141 | }
142 |
143 | export default Solution;
144 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-04-custom-hooks/Problem.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 4: Custom Hooks and Code Reusability
5 | *
6 | * PROBLEM:
7 | * You have two components that fetch data from different APIs.
8 | * Both components have similar logic for loading, error handling, and data fetching.
9 | *
10 | * Question: What's wrong with this code?
11 | *
12 | * Options:
13 | * A) Nothing is wrong, this is the correct way
14 | * B) Code duplication - should extract into a custom hook
15 | * C) Missing cleanup in useEffect
16 | * D) Should use fetch instead of hardcoded data
17 | *
18 | * BONUS: How would you create a reusable custom hook for this pattern?
19 | */
20 |
21 | // Component 1: Fetches user data
22 | function UserProfile() {
23 | const [data, setData] = useState(null);
24 | const [loading, setLoading] = useState(true);
25 | const [error, setError] = useState(null);
26 |
27 | useEffect(() => {
28 | setLoading(true);
29 | // Simulating API call
30 | setTimeout(() => {
31 | try {
32 | setData({ name: 'John Doe', email: 'john@example.com' });
33 | setLoading(false);
34 | } catch (err) {
35 | setError(err.message);
36 | setLoading(false);
37 | }
38 | }, 1000);
39 | }, []);
40 |
41 | if (loading) return Loading user...
;
42 | if (error) return Error: {error}
;
43 | return (
44 |
45 |
User Profile
46 |
Name: {data?.name}
47 |
Email: {data?.email}
48 |
49 | );
50 | }
51 |
52 | // Component 2: Fetches posts data (DUPLICATE LOGIC!)
53 | function PostsList() {
54 | const [data, setData] = useState(null);
55 | const [loading, setLoading] = useState(true);
56 | const [error, setError] = useState(null);
57 |
58 | useEffect(() => {
59 | setLoading(true);
60 | // Simulating API call
61 | setTimeout(() => {
62 | try {
63 | setData([
64 | { id: 1, title: 'First Post' },
65 | { id: 2, title: 'Second Post' }
66 | ]);
67 | setLoading(false);
68 | } catch (err) {
69 | setError(err.message);
70 | setLoading(false);
71 | }
72 | }, 1000);
73 | }, []);
74 |
75 | if (loading) return Loading posts...
;
76 | if (error) return Error: {error}
;
77 | return (
78 |
79 |
Posts
80 | {data?.map(post => (
81 |
• {post.title}
82 | ))}
83 |
84 | );
85 | }
86 |
87 | function Problem() {
88 | return (
89 |
90 |
Exercise 4: Custom Hooks Problem
91 |
92 |
96 |
97 |
104 |
⚠️ Question: What's the problem with this code?
105 |
Think about:
106 |
107 | Is there code duplication?
108 | How would you make this reusable?
109 | What would a custom hook look like?
110 | What are the benefits of extracting this logic?
111 |
112 |
113 | Hint: Look at the similar patterns in both components
114 |
115 |
116 |
117 |
123 |
❌ Code Smell Detected
124 |
125 | Both components have identical state management (data, loading, error)
126 | Both have the same useEffect pattern
127 | Both have the same conditional rendering logic
128 | If we need to add a feature (e.g., retry), we'd have to update both!
129 |
130 |
131 |
132 | );
133 | }
134 |
135 | export default Problem;
136 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-01-state-batching/Solution.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 1: State Batching and Event Handlers - SOLUTION
5 | *
6 | * ANSWER: C) Counter: 1, Alert shows: 0
7 | *
8 | * EXPLANATION:
9 | *
10 | * 1. STATE CLOSURE:
11 | * - When the event handler runs, `counter` is captured with its current value (0)
12 | * - All three setCounter calls use the same captured value: counter + 1 = 0 + 1 = 1
13 | * - React batches these updates and only applies the last one
14 | *
15 | * 2. ALERT TIMING:
16 | * - The alert executes synchronously before React re-renders
17 | * - So it shows the old value (0), not the new value (1)
18 | *
19 | * 3. BATCHING:
20 | * - React batches multiple setState calls in event handlers for performance
21 | * - Since all three calls set the state to the same value (1), the result is just 1
22 | *
23 | * CORRECT APPROACH (Functional Updates):
24 | * Use the functional form of setState to access the previous state value
25 | */
26 |
27 | function Solution() {
28 | const [counter, setCounter] = useState(0);
29 | const [correctCounter, setCorrectCounter] = useState(0);
30 |
31 | // WRONG: Uses stale closure value
32 | const handleIncrementWrong = () => {
33 | setCounter(counter + 1);
34 | setCounter(counter + 1);
35 | setCounter(counter + 1);
36 | alert(`Wrong approach - Counter value in alert: ${counter}`);
37 | };
38 |
39 | // CORRECT: Uses functional updates
40 | const handleIncrementCorrect = () => {
41 | setCorrectCounter(prev => prev + 1);
42 | setCorrectCounter(prev => prev + 1);
43 | setCorrectCounter(prev => prev + 1);
44 | // Note: Alert still shows old value because state updates are async
45 | alert(`Correct approach - Counter value in alert: ${correctCounter} (old value)`);
46 | };
47 |
48 | return (
49 |
50 |
Exercise 1: State Batching Solution
51 |
52 |
53 |
❌ Wrong Approach (Closure Problem)
54 |
Current Counter: {counter}
55 |
Increment (Wrong)
56 |
57 | This only increments by 1 because all three setState calls use the same captured value.
58 |
59 |
60 |
61 |
62 |
✅ Correct Approach (Functional Updates)
63 |
Current Counter: {correctCounter}
64 |
Increment (Correct)
65 |
66 | This increments by 3 because each setState receives the previous state value.
67 |
68 |
69 |
70 |
71 |
📚 Key Takeaways
72 |
73 | State is a snapshot: The state value doesn't change during a render
74 | Closures capture values: Event handlers capture the state value from when they were created
75 | Use functional updates: When new state depends on previous state, use the function form
76 | Batching: React batches multiple setState calls in event handlers
77 | Async updates: State updates are asynchronous - you can't read the new value immediately
78 |
79 |
80 |
81 |
82 |
💡 Code Comparison
83 |
84 | {`// ❌ WRONG - Uses stale closure
85 | setCounter(counter + 1); // 0 + 1 = 1
86 | setCounter(counter + 1); // 0 + 1 = 1
87 | setCounter(counter + 1); // 0 + 1 = 1
88 | // Result: counter = 1
89 |
90 | // ✅ CORRECT - Uses previous state
91 | setCounter(prev => prev + 1); // 0 + 1 = 1
92 | setCounter(prev => prev + 1); // 1 + 1 = 2
93 | setCounter(prev => prev + 1); // 2 + 1 = 3
94 | // Result: counter = 3`}
95 |
96 |
97 |
98 | );
99 | }
100 |
101 | export default Solution;
102 |
--------------------------------------------------------------------------------
/coding-exercise/QUICK_START.md:
--------------------------------------------------------------------------------
1 | # Quick Start Guide - Adding New Exercises
2 |
3 | ## 🚀 5-Minute Setup
4 |
5 | ### Step 1: Copy Template (30 seconds)
6 | ```bash
7 | # Copy the template folder
8 | cp -r src/exercises/__template__ src/exercises/exercise-03-your-topic
9 | ```
10 |
11 | ### Step 2: Edit Files (3 minutes)
12 |
13 | **Edit `Problem.js`:**
14 | - Line 4: Change exercise title
15 | - Line 7-14: Update problem description and options
16 | - Line 19-30: Add your problem code
17 |
18 | **Edit `Solution.js`:**
19 | - Line 4: Change exercise title
20 | - Line 6: Add correct answer
21 | - Line 8-18: Write explanation
22 | - Line 24-80: Add solution code and examples
23 |
24 | ### Step 3: Register Exercise (1 minute)
25 |
26 | **Edit `src/exercises/index.js`:**
27 |
28 | ```javascript
29 | // 1. Import your exercise
30 | import * as Exercise03 from './exercise-03-your-topic';
31 |
32 | // 2. Add to exercises array
33 | export const exercises = [
34 | // ... existing exercises
35 | {
36 | id: 'exercise-03',
37 | title: 'Your Topic Name',
38 | description: 'What this exercise teaches',
39 | difficulty: 'Medium', // Easy, Medium, or Hard
40 | topics: ['Hook1', 'Concept2', 'Pattern3'],
41 | Problem: Exercise03.Problem,
42 | Solution: Exercise03.Solution,
43 | },
44 | ];
45 | ```
46 |
47 | ### Step 4: Test (30 seconds)
48 | ```bash
49 | npm start
50 | ```
51 |
52 | Visit http://localhost:3000 and your exercise should appear!
53 |
54 | ## 📋 Checklist
55 |
56 | - [ ] Copied template folder with correct naming
57 | - [ ] Updated Problem.js with question and code
58 | - [ ] Updated Solution.js with answer and explanation
59 | - [ ] Registered in exercises/index.js
60 | - [ ] Tested in browser
61 | - [ ] No console errors
62 |
63 | ## 🎯 Exercise Naming Convention
64 |
65 | ```
66 | exercise-[NUMBER]-[topic-name]
67 |
68 | Examples:
69 | ✅ exercise-03-useCallback-optimization
70 | ✅ exercise-04-context-api-pitfalls
71 | ✅ exercise-05-custom-hooks
72 | ❌ Exercise3
73 | ❌ exercise_3_useCallback
74 | ❌ 03-useCallback
75 | ```
76 |
77 | ## 💡 Quick Tips
78 |
79 | 1. **Keep it focused**: One concept per exercise
80 | 2. **Make it interactive**: Add buttons, inputs, etc.
81 | 3. **Show comparisons**: Wrong vs. Right approach
82 | 4. **Explain thoroughly**: Don't just show code, explain why
83 | 5. **Use console.log**: Help users debug and learn
84 |
85 | ## 🎨 Color Codes (Copy-Paste Ready)
86 |
87 | ```javascript
88 | // Problem/Warning
89 | backgroundColor: '#fff3cd', border: '1px solid #ffc107'
90 |
91 | // Wrong Approach
92 | backgroundColor: '#ffe6e6'
93 |
94 | // Correct Solution
95 | backgroundColor: '#e6ffe6'
96 |
97 | // Information/Tips
98 | backgroundColor: '#e6f3ff'
99 |
100 | // Key Takeaways
101 | backgroundColor: '#f0f0f0'
102 | ```
103 |
104 | ## 📝 Difficulty Guidelines
105 |
106 | **Easy**: Basic React concepts, straightforward answers
107 | - useState basics
108 | - Props passing
109 | - Simple event handlers
110 |
111 | **Medium**: Intermediate concepts, requires understanding
112 | - State closures
113 | - useEffect dependencies
114 | - Memoization basics
115 |
116 | **Hard**: Advanced patterns, tricky edge cases
117 | - Complex state management
118 | - Performance optimization
119 | - Advanced hooks patterns
120 |
121 | ## 🔍 Topic Suggestions
122 |
123 | Common interview topics:
124 | - useState, useEffect, useRef, useCallback, useMemo
125 | - Closures, State Batching, Event Handlers
126 | - Dependencies, Infinite Loops, Memory Leaks
127 | - Performance, Memoization, Re-renders
128 | - Context API, Custom Hooks, Composition
129 |
130 | ## ⚡ Common Mistakes to Avoid
131 |
132 | ❌ Don't hardcode exercise numbers in multiple places
133 | ✅ Use the exercise ID consistently
134 |
135 | ❌ Don't forget to export in index.js
136 | ✅ Always add both Problem and Solution exports
137 |
138 | ❌ Don't skip the explanation
139 | ✅ Always explain WHY, not just WHAT
140 |
141 | ❌ Don't use external dependencies without updating package.json
142 | ✅ Keep exercises self-contained with inline styles
143 |
144 | ## 🆘 Troubleshooting
145 |
146 | **Exercise doesn't appear on homepage:**
147 | - Check if registered in `exercises/index.js`
148 | - Verify import path is correct
149 | - Ensure index.js exports both Problem and Solution
150 |
151 | **"Exercise not found" error:**
152 | - Check exercise ID matches folder name (without 'exercise-' prefix)
153 | - Verify ID in registry matches navigation string
154 |
155 | **Styling looks broken:**
156 | - Use inline styles for consistency
157 | - Copy styles from existing exercises
158 | - Test in browser dev tools
159 |
160 | ## 📚 Full Documentation
161 |
162 | For detailed information, see [STRUCTURE.md](./STRUCTURE.md)
163 |
164 | ---
165 |
166 | Need help? Check existing exercises for examples! 🎓
167 |
--------------------------------------------------------------------------------
/coding-exercise/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-02-useeffect-dependencies/Solution.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 2: useEffect Dependencies and Infinite Loops - SOLUTION
5 | *
6 | * ANSWER: B) The count increments infinitely causing an infinite loop
7 | *
8 | * EXPLANATION:
9 | *
10 | * 1. OBJECT REFERENCE PROBLEM:
11 | * - setData({ value: count }) creates a NEW object every time
12 | * - React compares dependencies using Object.is() (similar to ===)
13 | * - { value: 0 } !== { value: 0 } (different references)
14 | *
15 | * 2. THE INFINITE LOOP:
16 | * - Effect runs → creates new data object → triggers re-render
17 | * - Re-render → data dependency changed → effect runs again
18 | * - This continues infinitely!
19 | *
20 | * 3. WHY IT HAPPENS:
21 | * - Objects and arrays are compared by reference, not by value
22 | * - Each render creates a new object reference
23 | * - useEffect sees a "different" dependency and runs again
24 | */
25 |
26 | function Solution() {
27 | // Example 1: Solution 1 - Remove object from dependencies
28 | const [solution1Count, setSolution1Count] = useState(0);
29 | const [solution1Data, setSolution1Data] = useState({ value: 0 });
30 |
31 | useEffect(() => {
32 | console.log('Solution 1: Effect runs once on mount');
33 | setSolution1Count(prev => prev + 1);
34 | setSolution1Data({ value: 0 });
35 | }, []); // Empty dependency array - runs once on mount
36 |
37 | // Example 2: Solution 2 - Use primitive values in dependencies
38 | const [solution2Count, setSolution2Count] = useState(0);
39 | const [solution2Data, setSolution2Data] = useState({ value: 0 });
40 | const [trigger, setTrigger] = useState(0);
41 |
42 | useEffect(() => {
43 | console.log('Solution 2: Effect runs when trigger changes');
44 | setSolution2Count(prev => prev + 1);
45 | setSolution2Data({ value: trigger });
46 | }, [trigger]); // Depend on primitive value, not object
47 |
48 | // Example 3: Solution 3 - Use useRef for mutable object
49 | const [solution3Count, setSolution3Count] = useState(0);
50 | const solution3DataRef = useRef({ value: 0 });
51 |
52 | useEffect(() => {
53 | console.log('Solution 3: Effect runs once, uses ref for mutable data');
54 | setSolution3Count(prev => prev + 1);
55 | solution3DataRef.current = { value: solution3Count };
56 | }, []); // Refs don't trigger re-renders
57 |
58 | return (
59 |
60 |
Exercise 2: useEffect Dependencies Solution
61 |
62 |
63 |
❌ The Problem (Code Disabled)
64 |
65 | {`useEffect(() => {
66 | setCount(count + 1);
67 | setData({ value: count }); // Creates NEW object
68 | }, [data]); // Object reference changes every time!
69 |
70 | // Result: INFINITE LOOP 🔄`}
71 |
72 |
73 | Why it loops: Each effect run creates a new object,
74 | which triggers the effect again because the reference changed.
75 |
76 |
77 |
78 |
79 |
✅ Solution 1: Empty Dependency Array
80 |
Count: {solution1Count}
81 |
Data Value: {solution1Data.value}
82 |
83 | {`useEffect(() => {
84 | setSolution1Count(prev => prev + 1);
85 | setSolution1Data({ value: 0 });
86 | }, []); // Empty array = runs once on mount`}
87 |
88 |
89 | Use when: You only need to run the effect once on component mount
90 |
91 |
92 |
93 |
94 |
✅ Solution 2: Depend on Primitive Values
95 |
Count: {solution2Count}
96 |
Data Value: {solution2Data.value}
97 |
setTrigger(prev => prev + 1)}>
98 | Trigger Effect (Trigger: {trigger})
99 |
100 |
101 | {`const [trigger, setTrigger] = useState(0);
102 |
103 | useEffect(() => {
104 | setSolution2Count(prev => prev + 1);
105 | setSolution2Data({ value: trigger });
106 | }, [trigger]); // Primitive value - safe to use`}
107 |
108 |
109 | Use when: You need to react to specific value changes
110 |
111 |
112 |
113 |
114 |
✅ Solution 3: Use useRef for Mutable Data
115 |
Count: {solution3Count}
116 |
Data Value (from ref): {solution3DataRef.current.value}
117 |
118 | {`const dataRef = useRef({ value: 0 });
119 |
120 | useEffect(() => {
121 | setSolution3Count(prev => prev + 1);
122 | dataRef.current = { value: count }; // Mutate ref
123 | }, []); // Refs don't cause re-renders`}
124 |
125 |
126 | Use when: You need to store mutable data without triggering re-renders
127 |
128 |
129 |
130 |
131 |
📚 Key Takeaways
132 |
133 | Objects/Arrays are compared by reference: {`{ } !== { }`} even if contents are the same
134 | Avoid objects in dependencies: They create new references on every render
135 | Use primitive values: Numbers, strings, booleans are compared by value
136 | Empty array []: Effect runs once on mount only
137 | No array: Effect runs after every render (usually not what you want)
138 | useRef for mutable data: Doesn't trigger re-renders when changed
139 | ESLint plugin: Use eslint-plugin-react-hooks to catch these issues
140 |
141 |
142 |
143 |
144 |
⚠️ Common Mistakes
145 |
146 | ❌ Putting objects/arrays directly in dependency array
147 | ❌ Creating new objects/arrays inside the component body
148 | ❌ Ignoring ESLint warnings about missing dependencies
149 | ✅ Extract primitive values from objects for dependencies
150 | ✅ Use useMemo/useCallback to stabilize object references
151 | ✅ Consider if you really need the dependency
152 |
153 |
154 |
155 | );
156 | }
157 |
158 | export default Solution;
159 |
--------------------------------------------------------------------------------
/coding-exercise/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | # Architecture Overview
2 |
3 | ## 📊 Visual Structure
4 |
5 | ```
6 | coding-exercise/
7 | │
8 | ├── 📄 STRUCTURE.md # Detailed documentation
9 | ├── 📄 QUICK_START.md # Quick guide for adding exercises
10 | ├── 📄 ARCHITECTURE.md # This file - architecture overview
11 | │
12 | └── src/
13 | ├── 📱 App.js # Main app with routing
14 | │ └── Uses: HomePage, NavigationButton, exercises registry
15 | │
16 | ├── 🧩 components/ # Reusable UI components
17 | │ ├── HomePage.js # Landing page
18 | │ ├── ExerciseCard.js # Exercise card component
19 | │ └── NavigationButton.js # Back button
20 | │
21 | └── 📚 exercises/ # All exercises
22 | ├── index.js # ⭐ EXERCISE REGISTRY (add new exercises here)
23 | │
24 | ├── __template__/ # Template for new exercises
25 | │ ├── Problem.js
26 | │ ├── Solution.js
27 | │ ├── index.js
28 | │ └── README.md
29 | │
30 | ├── exercise-01-state-batching/
31 | │ ├── Problem.js
32 | │ ├── Solution.js
33 | │ └── index.js
34 | │
35 | └── exercise-02-useeffect-dependencies/
36 | ├── Problem.js
37 | ├── Solution.js
38 | └── index.js
39 | ```
40 |
41 | ## 🔄 Data Flow
42 |
43 | ```
44 | ┌─────────────────────────────────────────────────────────────┐
45 | │ App.js │
46 | │ - Manages currentView state │
47 | │ - Routes between home and exercise views │
48 | └─────────────────────────────────────────────────────────────┘
49 | │
50 | ├─── home ────────────────────┐
51 | │ │
52 | │ ▼
53 | │ ┌──────────────────┐
54 | │ │ HomePage.js │
55 | │ │ - Lists all │
56 | │ │ exercises │
57 | │ │ - Uses │
58 | │ │ ExerciseCard │
59 | │ └──────────────────┘
60 | │ │
61 | │ │ reads from
62 | │ ▼
63 | │ ┌──────────────────┐
64 | │ │ exercises/ │
65 | │ │ index.js │
66 | │ │ (Registry) │
67 | │ └──────────────────┘
68 | │
69 | └─── exercise-XX-problem ─────┐
70 | └─── exercise-XX-solution ────┤
71 | │
72 | ▼
73 | ┌──────────────────────────┐
74 | │ Dynamically loads │
75 | │ Problem or Solution │
76 | │ component from registry │
77 | └──────────────────────────┘
78 | ```
79 |
80 | ## 🏗️ Component Hierarchy
81 |
82 | ```
83 | App
84 | ├── NavigationButton (when not on home)
85 | └── Current View
86 | ├── HomePage
87 | │ └── ExerciseCard (for each exercise)
88 | │ ├── View Problem button
89 | │ └── View Solution button
90 | │
91 | └── Exercise Component (Problem or Solution)
92 | └── Interactive React components
93 | ```
94 |
95 | ## 🎯 Key Design Decisions
96 |
97 | ### 1. **Centralized Registry Pattern**
98 | - **File**: `exercises/index.js`
99 | - **Why**: Single source of truth for all exercises
100 | - **Benefit**: Add exercises without touching App.js
101 |
102 | ### 2. **Folder-per-Exercise Structure**
103 | - **Pattern**: `exercise-XX-topic-name/`
104 | - **Why**: Self-contained, easy to find and maintain
105 | - **Benefit**: Clear organization as project scales
106 |
107 | ### 3. **Component Reusability**
108 | - **Location**: `components/`
109 | - **Why**: Shared UI components
110 | - **Benefit**: Consistent styling and behavior
111 |
112 | ### 4. **Template-Based Creation**
113 | - **Location**: `exercises/__template__/`
114 | - **Why**: Standardized structure
115 | - **Benefit**: Fast exercise creation, consistent format
116 |
117 | ### 5. **Dynamic Routing**
118 | - **Implementation**: String-based view state
119 | - **Why**: Simple, no external router needed
120 | - **Benefit**: Lightweight, easy to understand
121 |
122 | ## 📦 Module Dependencies
123 |
124 | ```
125 | App.js
126 | ↓ imports
127 | ├── HomePage (from components/)
128 | ├── NavigationButton (from components/)
129 | └── exercises (from exercises/index.js)
130 |
131 | HomePage.js
132 | ↓ imports
133 | ├── ExerciseCard (from components/)
134 | └── exercises (from exercises/index.js)
135 |
136 | exercises/index.js
137 | ↓ imports
138 | ├── exercise-01-state-batching
139 | ├── exercise-02-useeffect-dependencies
140 | └── ... (more exercises)
141 |
142 | Each exercise folder
143 | ↓ exports via index.js
144 | ├── Problem component
145 | └── Solution component
146 | ```
147 |
148 | ## 🔑 Key Files
149 |
150 | | File | Purpose | When to Edit |
151 | |------|---------|--------------|
152 | | `exercises/index.js` | Exercise registry | **Every time** you add a new exercise |
153 | | `exercises/__template__/` | Template files | When creating a new exercise (copy it) |
154 | | `components/ExerciseCard.js` | Exercise card UI | To change how exercises are displayed |
155 | | `components/HomePage.js` | Home page layout | To change home page structure |
156 | | `App.js` | Main routing logic | Rarely (architecture is stable) |
157 |
158 | ## 🚀 Scaling Strategy
159 |
160 | ### Adding 10 Exercises
161 | ✅ Current structure handles this perfectly
162 | - Just add folders and register in index.js
163 |
164 | ### Adding 50 Exercises
165 | ✅ Current structure still works
166 | - Consider adding categories in registry
167 | - Group exercises by difficulty or topic
168 |
169 | ### Adding 100+ Exercises
170 | ⚠️ Consider enhancements:
171 | - Add search/filter functionality
172 | - Implement category pages
173 | - Add pagination
174 | - Create difficulty-based navigation
175 |
176 | ## 🎨 Styling Strategy
177 |
178 | **Current**: Inline styles
179 | - ✅ No CSS conflicts
180 | - ✅ Component-scoped
181 | - ✅ Easy to copy/paste
182 | - ✅ Works without build config
183 |
184 | **Future**: If needed
185 | - CSS Modules for shared styles
186 | - Styled Components for complex styling
187 | - Tailwind CSS for utility classes
188 |
189 | ## 🧪 Testing Strategy
190 |
191 | ### Current Setup
192 | - Manual testing in browser
193 | - Visual verification
194 |
195 | ### Recommended Additions
196 | ```javascript
197 | // Example test structure
198 | describe('Exercise 01', () => {
199 | it('renders problem correctly', () => {
200 | // Test problem component
201 | });
202 |
203 | it('renders solution correctly', () => {
204 | // Test solution component
205 | });
206 | });
207 | ```
208 |
209 | ## 📈 Performance Considerations
210 |
211 | ### Current Optimizations
212 | - ✅ No unnecessary re-renders (simple state management)
213 | - ✅ Lazy loading not needed (small bundle size)
214 | - ✅ Inline styles (no CSS parsing overhead)
215 |
216 | ### Future Optimizations (if needed)
217 | - React.lazy() for code splitting
218 | - useMemo for expensive calculations
219 | - Virtual scrolling for large exercise lists
220 |
221 | ## 🔒 Best Practices
222 |
223 | 1. **One Exercise = One Folder**: Keep exercises self-contained
224 | 2. **Register Immediately**: Add to registry right after creating
225 | 3. **Follow Naming**: Use `exercise-XX-topic-name` format
226 | 4. **Test Before Commit**: Always run `npm start` to verify
227 | 5. **Document Complex Logic**: Add comments for tricky code
228 | 6. **Keep Templates Updated**: Update `__template__` with improvements
229 |
230 | ## 🎓 Learning Path
231 |
232 | For new contributors:
233 | 1. Read `QUICK_START.md` (5 min)
234 | 2. Browse existing exercises (10 min)
235 | 3. Copy template and create test exercise (15 min)
236 | 4. Read `STRUCTURE.md` for details (10 min)
237 | 5. Review this architecture doc (5 min)
238 |
239 | Total: ~45 minutes to full productivity
240 |
241 | ## 🤝 Contributing Guidelines
242 |
243 | 1. **Before adding an exercise**:
244 | - Check if topic already exists
245 | - Ensure it's interview-relevant
246 | - Verify it teaches one clear concept
247 |
248 | 2. **When adding an exercise**:
249 | - Use the template
250 | - Follow naming conventions
251 | - Test thoroughly
252 | - Add clear explanations
253 |
254 | 3. **After adding an exercise**:
255 | - Verify navigation works
256 | - Check for console errors
257 | - Test on different screen sizes
258 | - Update documentation if needed
259 |
260 | ---
261 |
262 | **Questions?** Check the other documentation files or review existing exercises for examples.
263 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-03-useCallback-memoization/Solution.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, memo } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 3: useCallback and Unnecessary Re-renders - SOLUTION
5 | *
6 | * ANSWER: B) ChildComponent re-renders every time count changes
7 | *
8 | * EXPLANATION:
9 | *
10 | * 1. FUNCTION REFERENCE PROBLEM:
11 | * - Every time the parent re-renders, handleClick is recreated
12 | * - Even though the function does the same thing, it's a NEW reference
13 | * - React compares props by reference: oldFunction !== newFunction
14 | *
15 | * 2. WHY IT RE-RENDERS:
16 | * - Parent count changes → Parent re-renders
17 | * - Parent re-render → handleClick recreated with new reference
18 | * - New reference → Child sees "different" prop → Child re-renders
19 | *
20 | * 3. THE SOLUTION:
21 | * - Use useCallback to memoize the function
22 | * - Use React.memo to prevent re-renders when props haven't changed
23 | * - Combine both for optimal performance
24 | */
25 |
26 | // Problem: Regular child component (re-renders on every parent render)
27 | const RegularChild = ({ onClick, label }) => {
28 | console.log(`${label} rendered`);
29 | return (
30 |
31 |
{label}
32 |
Click me
33 |
34 | );
35 | };
36 |
37 | // Solution 1: Memoized child (still re-renders because function reference changes)
38 | const MemoizedChild = memo(({ onClick, label }) => {
39 | console.log(`${label} rendered`);
40 | return (
41 |
42 |
{label}
43 |
Click me
44 |
45 | );
46 | });
47 |
48 | // Solution 2: Optimized child (won't re-render unnecessarily)
49 | const OptimizedChild = memo(({ onClick, label }) => {
50 | console.log(`${label} rendered`);
51 | return (
52 |
53 |
{label}
54 |
Click me
55 |
56 | );
57 | });
58 |
59 | function Solution() {
60 | const [count, setCount] = useState(0);
61 | const [clicks1, setClicks1] = useState(0);
62 | const [clicks2, setClicks2] = useState(0);
63 | const [clicks3, setClicks3] = useState(0);
64 |
65 | // ❌ WRONG: Function recreated on every render
66 | const handleClick1 = () => {
67 | setClicks1(prev => prev + 1);
68 | };
69 |
70 | // ⚠️ PARTIAL: Function recreated on every render (memo doesn't help)
71 | const handleClick2 = () => {
72 | setClicks2(prev => prev + 1);
73 | };
74 |
75 | // ✅ CORRECT: Function memoized with useCallback
76 | const handleClick3 = useCallback(() => {
77 | setClicks3(prev => prev + 1);
78 | }, []); // Empty deps = function never changes
79 |
80 | return (
81 |
82 |
Exercise 3: useCallback & Memoization Solution
83 |
84 |
90 |
Parent State
91 |
Parent Count: {count}
92 |
setCount(count + 1)}>
93 | Increment Parent Count (Watch Console!)
94 |
95 |
96 |
97 | {/* Example 1: Regular child without memo */}
98 |
99 |
❌ Problem: Regular Child (No Optimization)
100 |
Clicks: {clicks1}
101 |
105 |
112 | {`const handleClick = () => {
113 | setClicks(prev => prev + 1);
114 | };
115 |
116 |
117 |
118 | // Problem: Function recreated every render
119 | // Child re-renders every time`}
120 |
121 |
122 |
123 | {/* Example 2: Memoized child but function still recreated */}
124 |
125 |
⚠️ Partial: Memo Child (Still Re-renders)
126 |
Clicks: {clicks2}
127 |
131 |
138 | {`const MemoizedChild = memo(({ onClick }) => {
139 | return Click ;
140 | });
141 |
142 | const handleClick = () => { /* ... */ };
143 |
144 |
145 | // Problem: memo() helps, but function still
146 | // recreated, so props still "change"`}
147 |
148 |
149 |
150 | {/* Example 3: Optimized with useCallback + memo */}
151 |
152 |
✅ Solution: useCallback + memo
153 |
Clicks: {clicks3}
154 |
158 |
165 | {`const OptimizedChild = memo(({ onClick }) => {
166 | return Click ;
167 | });
168 |
169 | const handleClick = useCallback(() => {
170 | setClicks(prev => prev + 1);
171 | }, []); // Memoized - same reference
172 |
173 |
174 |
175 | // ✅ Function reference stays the same
176 | // ✅ memo() prevents re-render
177 | // ✅ Only re-renders when actually needed`}
178 |
179 |
180 |
181 | {/* Key Takeaways */}
182 |
188 |
📚 Key Takeaways
189 |
190 | Functions are recreated: On every render, function declarations create new references
191 | Reference equality: React compares props using === (reference comparison)
192 | useCallback: Memoizes function references between renders
193 | React.memo: Prevents re-renders when props haven't changed
194 | Combine both: useCallback + memo for optimal performance
195 | Dependencies matter: useCallback deps determine when function updates
196 |
197 |
198 |
199 | {/* When to Use */}
200 |
206 |
💡 When to Use useCallback
207 |
208 | ✅ Passing callbacks to memoized child components
209 | ✅ Function is a dependency in useEffect/useMemo
210 | ✅ Expensive child components that re-render often
211 | ❌ Simple components without performance issues
212 | ❌ Functions that change on every render anyway
213 | ❌ Premature optimization (measure first!)
214 |
215 |
216 |
217 | {/* Common Mistakes */}
218 |
224 |
⚠️ Common Mistakes
225 |
226 | ❌ Using useCallback without React.memo on child
227 | ❌ Forgetting dependencies (causes stale closures)
228 | ❌ Over-optimizing everything (adds complexity)
229 | ❌ Using useCallback for functions that change often
230 | ✅ Profile first, optimize when needed
231 | ✅ Use React DevTools Profiler to find bottlenecks
232 |
233 |
234 |
235 | {/* Code Comparison */}
236 |
242 |
📊 Performance Comparison
243 |
244 |
245 |
246 | Approach
247 | Re-renders
248 | Performance
249 |
250 |
251 |
252 |
253 | Regular Child
254 | ❌ Every parent render
255 | Poor
256 |
257 |
258 | memo() only
259 | ❌ Every parent render
260 | Poor
261 |
262 |
263 | useCallback + memo
264 | ✅ Only when needed
265 | Excellent
266 |
267 |
268 |
269 |
270 |
271 | );
272 | }
273 |
274 | export default Solution;
275 |
--------------------------------------------------------------------------------
/coding-exercise/src/exercises/exercise-04-custom-hooks/Solution.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | /**
4 | * CODING EXERCISE 4: Custom Hooks and Code Reusability - SOLUTION
5 | *
6 | * ANSWER: B) Code duplication - should extract into a custom hook
7 | *
8 | * EXPLANATION:
9 | *
10 | * 1. THE PROBLEM:
11 | * - Both components have identical data fetching logic
12 | * - Duplicate state management (data, loading, error)
13 | * - Hard to maintain - changes need to be made in multiple places
14 | *
15 | * 2. THE SOLUTION:
16 | * - Extract common logic into a custom hook
17 | * - Custom hooks start with "use" prefix
18 | * - Can use other hooks inside (useState, useEffect, etc.)
19 | * - Returns values that components need
20 | *
21 | * 3. BENEFITS:
22 | * - DRY (Don't Repeat Yourself)
23 | * - Easier to test
24 | * - Easier to maintain
25 | * - Reusable across components
26 | */
27 |
28 | // ✅ SOLUTION: Custom Hook
29 | function useFetch(fetchFunction, dependencies = []) {
30 | const [data, setData] = useState(null);
31 | const [loading, setLoading] = useState(true);
32 | const [error, setError] = useState(null);
33 |
34 | useEffect(() => {
35 | let cancelled = false;
36 |
37 | const fetchData = async () => {
38 | setLoading(true);
39 | setError(null);
40 |
41 | try {
42 | const result = await fetchFunction();
43 | if (!cancelled) {
44 | setData(result);
45 | setLoading(false);
46 | }
47 | } catch (err) {
48 | if (!cancelled) {
49 | setError(err.message);
50 | setLoading(false);
51 | }
52 | }
53 | };
54 |
55 | fetchData();
56 |
57 | // Cleanup function to prevent state updates on unmounted component
58 | return () => {
59 | cancelled = true;
60 | };
61 | }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
62 |
63 | return { data, loading, error };
64 | }
65 |
66 | // Simulated API functions
67 | const fetchUser = () => {
68 | return new Promise((resolve) => {
69 | setTimeout(() => {
70 | resolve({ name: 'John Doe', email: 'john@example.com', role: 'Developer' });
71 | }, 1000);
72 | });
73 | };
74 |
75 | const fetchPosts = () => {
76 | return new Promise((resolve) => {
77 | setTimeout(() => {
78 | resolve([
79 | { id: 1, title: 'Understanding React Hooks', likes: 42 },
80 | { id: 2, title: 'Custom Hooks Best Practices', likes: 38 },
81 | { id: 3, title: 'Building Reusable Components', likes: 55 }
82 | ]);
83 | }, 1200);
84 | });
85 | };
86 |
87 | const fetchComments = () => {
88 | return new Promise((resolve) => {
89 | setTimeout(() => {
90 | resolve([
91 | { id: 1, text: 'Great article!', author: 'Alice' },
92 | { id: 2, text: 'Very helpful, thanks!', author: 'Bob' }
93 | ]);
94 | }, 800);
95 | });
96 | };
97 |
98 | // ✅ Component 1: Using custom hook
99 | function UserProfile() {
100 | const { data, loading, error } = useFetch(fetchUser, []);
101 |
102 | if (loading) return Loading user...
;
103 | if (error) return Error: {error}
;
104 |
105 | return (
106 |
107 |
👤 User Profile
108 |
Name: {data?.name}
109 |
Email: {data?.email}
110 |
Role: {data?.role}
111 |
112 | );
113 | }
114 |
115 | // ✅ Component 2: Using custom hook
116 | function PostsList() {
117 | const { data, loading, error } = useFetch(fetchPosts, []);
118 |
119 | if (loading) return Loading posts...
;
120 | if (error) return Error: {error}
;
121 |
122 | return (
123 |
124 |
📝 Posts
125 | {data?.map(post => (
126 |
127 | {post.title} - {post.likes} likes
128 |
129 | ))}
130 |
131 | );
132 | }
133 |
134 | // ✅ Component 3: Using custom hook (easy to add new features!)
135 | function CommentsList() {
136 | const { data, loading, error } = useFetch(fetchComments, []);
137 |
138 | if (loading) return Loading comments...
;
139 | if (error) return Error: {error}
;
140 |
141 | return (
142 |
143 |
💬 Comments
144 | {data?.map(comment => (
145 |
146 | "{comment.text}" - {comment.author}
147 |
148 | ))}
149 |
150 | );
151 | }
152 |
153 | function Solution() {
154 | return (
155 |
156 |
Exercise 4: Custom Hooks Solution
157 |
158 | {/* The Solution */}
159 |
165 |
✅ Solution: useFetch Custom Hook
166 |
173 | {`function useFetch(fetchFunction, dependencies = []) {
174 | const [data, setData] = useState(null);
175 | const [loading, setLoading] = useState(true);
176 | const [error, setError] = useState(null);
177 |
178 | useEffect(() => {
179 | let cancelled = false;
180 |
181 | const fetchData = async () => {
182 | setLoading(true);
183 | setError(null);
184 |
185 | try {
186 | const result = await fetchFunction();
187 | if (!cancelled) {
188 | setData(result);
189 | setLoading(false);
190 | }
191 | } catch (err) {
192 | if (!cancelled) {
193 | setError(err.message);
194 | setLoading(false);
195 | }
196 | }
197 | };
198 |
199 | fetchData();
200 |
201 | return () => { cancelled = true; };
202 | }, dependencies);
203 |
204 | return { data, loading, error };
205 | }`}
206 |
207 |
208 |
209 | {/* Live Examples */}
210 |
211 |
🎯 Live Examples Using Custom Hook
212 |
213 |
214 |
215 |
216 |
217 | {/* Usage Example */}
218 |
224 |
💡 How to Use
225 |
232 | {`// Define your fetch function
233 | const fetchUser = () => {
234 | return fetch('/api/user').then(res => res.json());
235 | };
236 |
237 | // Use the custom hook in your component
238 | function UserProfile() {
239 | const { data, loading, error } = useFetch(fetchUser, []);
240 |
241 | if (loading) return Loading...
;
242 | if (error) return Error: {error}
;
243 | return {data.name}
;
244 | }`}
245 |
246 |
247 |
248 | {/* Key Takeaways */}
249 |
255 |
📚 Key Takeaways
256 |
257 | Custom hooks extract reusable logic: Share stateful logic between components
258 | Naming convention: Always start with "use" prefix (e.g., useFetch, useForm)
259 | Can use other hooks: useState, useEffect, useContext, etc.
260 | Return what components need: Usually an object or array of values
261 | Cleanup is important: Prevent memory leaks with cleanup functions
262 | Dependencies matter: Pass dependencies to control when hook re-runs
263 | Testable: Custom hooks can be tested independently
264 |
265 |
266 |
267 | {/* Before vs After */}
268 |
274 |
📊 Before vs After Comparison
275 |
276 |
277 |
278 | Aspect
279 | Before (Duplicated)
280 | After (Custom Hook)
281 |
282 |
283 |
284 |
285 | Code Lines
286 | ~30 lines per component
287 | ~5 lines per component
288 |
289 |
290 | Maintainability
291 | ❌ Update in multiple places
292 | ✅ Update in one place
293 |
294 |
295 | Testability
296 | ❌ Test each component
297 | ✅ Test hook once
298 |
299 |
300 | Reusability
301 | ❌ Copy-paste code
302 | ✅ Import and use
303 |
304 |
305 | Bug Fixes
306 | ❌ Fix in all places
307 | ✅ Fix once
308 |
309 |
310 |
311 |
312 |
313 | {/* Common Custom Hooks */}
314 |
320 |
🔧 Common Custom Hook Patterns
321 |
322 | useFetch: Data fetching with loading/error states
323 | useForm: Form state management and validation
324 | useLocalStorage: Sync state with localStorage
325 | useDebounce: Debounce values (e.g., search input)
326 | useWindowSize: Track window dimensions
327 | useOnClickOutside: Detect clicks outside element
328 | usePrevious: Store previous value of a prop/state
329 | useToggle: Boolean state with toggle function
330 |
331 |
332 |
333 | {/* Best Practices */}
334 |
339 |
✨ Custom Hook Best Practices
340 |
341 | ✅ Start with "use": Required for React to recognize it as a hook
342 | ✅ Keep it focused: One responsibility per hook
343 | ✅ Return objects for flexibility: Easy to add new values later
344 | ✅ Add cleanup: Always clean up side effects
345 | ✅ Document parameters: Make it clear what the hook expects
346 | ✅ Handle edge cases: Loading, errors, empty states
347 | ❌ Don't call conditionally: Hooks must be called in the same order
348 | ❌ Don't call in loops: Violates Rules of Hooks
349 |
350 |
351 |
352 | );
353 | }
354 |
355 | export default Solution;
356 |
--------------------------------------------------------------------------------
/coding-exercise/README.md:
--------------------------------------------------------------------------------
1 | ### Coding Exercise
2 |
3 | #### 1. What is the output of below code
4 |
5 | ```javascript
6 | import { useState } from 'react';
7 |
8 | export default function Counter() {
9 | const [counter, setCounter] = useState(5);
10 |
11 | return (
12 | <>
13 | {counter}
14 | {
15 | setCounter(counter + 5);
16 | setCounter(counter + 5);
17 | alert(counter);
18 | setCounter(counter + 5);
19 | setCounter(counter + 5);
20 | }}>Increment
21 | >
22 | )
23 | }
24 | ```
25 |
26 | - 1: Alert with 5, 5
27 | - 2: Alert with 15, 25
28 | - 3: Alert with 5, 10
29 | - 4: Error: Cannot update the same state multiple times concurrently
30 |
31 | Answer
32 |
33 |
34 | ##### Answer: 3
35 | State values are fixed(i.e, default value 5) in each render and setting the state only changes it for the next render. React will wait until all the code executed with in an event handler before your state updates followed by re-rendering the UI. Also, all the 3 setter function calls are replacing the calculated value. Hence, irrespective of how many times you call `setCounter(counter + 5)` the final value is 10(5+5).
36 |
37 | This can be visuallized by substituting with state variable values in the particular render,
38 | ```javascript
39 | {
40 | setCounter(5 + 5);
41 | setCounter(5 + 5);
42 | alert(5);
43 | setCounter(5 + 5);
44 | setCounter(5 + 5);
45 | }}>Increment
46 | ```
47 |
48 |
49 |
50 | ---
51 |
52 | **[⬆ Back to Top](#table-of-contents)**
53 |
54 | #### 2. What is the output of below code
55 |
56 | ```javascript
57 | import { useState } from 'react';
58 |
59 | export default function Counter() {
60 | const [counter, setCounter] = useState(5);
61 |
62 | return (
63 | <>
64 | {counter}
65 | {
66 | setCounter(counter => counter + 5);
67 | setCounter(counter => counter + 5);
68 | alert(counter);
69 | setCounter(counter => counter + 5);
70 | setCounter(counter => counter + 5);
71 | }}>Increment
72 | >
73 | )
74 | }
75 | ```
76 |
77 | - 1: Alert with 5, 25
78 | - 2: Alert with 5, 10
79 | - 3: Alert with 15, 25
80 | - 4: Alert with 15, 10
81 |
82 | Answer
83 |
84 |
85 | ##### Answer: 1
86 |
87 | React queues all the updater functions(e.g, counter => counter + 5) which will be processed after all the code inside event handler has been executed. During the next re-render(state update through setState), React goes through the queue and increment the counter based on the previous value in each function call. So the final value of counter becomes 25(initial value 5 + 5 + 5 + 5 + 5) whereas the alert shows default value 5 because the counter value won't be updated by that time.
88 |
89 |
90 |
91 |
92 | ---
93 |
94 | **[⬆ Back to Top](#table-of-contents)**
95 |
96 | #### 3. What is the output of span after one click?
97 |
98 | ```javascript
99 | import { useRef } from 'react';
100 |
101 | export default function Counter() {
102 | let countRef = useRef(0);
103 |
104 | function handleIncrement() {
105 | countRef.current = countRef.current + 1;
106 | }
107 |
108 | return (
109 | <>
110 | Count: {countRef.current}
111 |
112 | Click me
113 |
114 | >
115 | )
116 | }
117 | ```
118 |
119 | - 1: Cannot read current property of undefined
120 | - 2: Count: 1
121 | - 3: null
122 | - 4: Count: 0
123 |
124 | Answer
125 |
126 |
127 | ##### Answer: 4
128 |
129 | In React, every update has two phases.
130 | 1. **Render:** This is where React calls the components in order to output something on the screen
131 | 2. **Commit:** React applies changes to the DOM
132 |
133 | Any updates to the ref will be reflected only in the commit phase. In other words, React sets **counterRef.current** during the commit phase. Hence, **countRef.current** always holds value `0` irrespective of how many times the Increment button clicked.
134 |
135 |
136 |
137 | ---
138 |
139 | **[⬆ Back to Top](#table-of-contents)**
140 |
141 | #### 4. What is the outcome of below code after button click?
142 |
143 | ```javascript
144 | import { useRef } from 'react';
145 |
146 | function MyCustomInput(props) {
147 | return ;
148 | }
149 |
150 | export default function MyCustomForm() {
151 | const inputRef = useRef(null);
152 |
153 | function handleInputFocus() {
154 | inputRef.current.focus();
155 | }
156 |
157 | return (
158 | <>
159 |
160 |
161 | Click Me
162 |
163 | >
164 | );
165 | }
166 | ```
167 |
168 | - 1: Input gets the focus
169 | - 2: Warning: Function components cannot be given refs.
170 | - 3: Cannot read current property of undefined
171 | - 4: Warning: Missing ref on element
172 |
173 | Answer
174 |
175 |
176 | ##### Answer: 2
177 | By default, React does not allow a component access the DOM nodes of other components even for child components. If you still try to access the DOM nodes directly then you will receive below error:
178 |
179 | ```javascript
180 | Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
181 | ```
182 |
183 | This issue can be fixed by wrapping the ** ** component with `forwardRef` function which accepts ref as the second argument which can be used on the ** ** element as **ref={ref}**
184 |
185 |
186 |
187 |
188 | ---
189 |
190 | **[⬆ Back to Top](#table-of-contents)**
191 |
192 | #### 5. What is the outcome of number of clicks after 3 button clicks?
193 |
194 | ```javascript
195 | import { useRef } from 'react';
196 |
197 | export default function Counter() {
198 | let ref = useRef(0);
199 |
200 | function handleClick() {
201 | ref.current = ref.current + 1;
202 | }
203 |
204 | return (
205 | <>
206 | Clicked {ref.current} times
207 |
208 | Click me!
209 |
210 | >
211 | );
212 | }
213 | ```
214 |
215 | - 1: 3 times
216 | - 2: 4 times
217 | - 3: 2 times
218 | - 4: 0 times
219 |
220 | Answer
221 |
222 |
223 | ##### Answer: 4
224 | If you try to use **{ref.current}** in the render method, the number won’t be updated on click. This is because **ref.current** does not trigger a re-render unlike state. This property is mainly used to read and write the values inside event handler or outside the render method.
225 |
226 |
227 |
228 | ---
229 |
230 | **[⬆ Back to Top](#table-of-contents)**
231 |
232 | #### 6. What is the output in the console after mounting and unmounting the component?
233 |
234 | ```javascript
235 | import { useEffect, useState } from 'react';
236 |
237 | export default function Timer() {
238 | const [count, setCount] = useState(0);
239 |
240 | useEffect(() => {
241 | console.log('Effect ran');
242 | const timer = setInterval(() => {
243 | setCount(c => c + 1);
244 | }, 1000);
245 |
246 | console.log('Cleanup registered');
247 | return () => {
248 | console.log('Cleanup executed');
249 | clearInterval(timer);
250 | };
251 | }, []);
252 |
253 | return Count: {count}
;
254 | }
255 | ```
256 |
257 | - 1: "Effect ran", "Cleanup registered", "Cleanup executed" (on mount), "Cleanup executed" (on unmount)
258 | - 2: "Effect ran", "Cleanup registered" (on mount), "Cleanup executed" (on unmount)
259 | - 3: "Effect ran" (on mount), "Cleanup executed" (on unmount)
260 | - 4: "Effect ran", "Cleanup registered", "Cleanup executed" (on mount and unmount)
261 |
262 | Answer
263 |
264 |
265 | ##### Answer: 2
266 |
267 | When a component mounts, React runs the effect function completely, which logs both "Effect ran" and "Cleanup registered". The cleanup function (returned from useEffect) is **not** executed during mount - it's only registered at this point.
268 |
269 | The cleanup function executes in two scenarios:
270 | 1. **Before re-running the effect** (if dependencies change)
271 | 2. **When the component unmounts**
272 |
273 | Since the dependency array is empty `[]`, the effect only runs once on mount and never re-runs. Therefore, the cleanup function will only execute when the component unmounts, logging "Cleanup executed" and clearing the interval timer.
274 |
275 | This pattern is crucial for preventing memory leaks when using timers, subscriptions, or event listeners in React components.
276 |
277 |
278 |
279 |
280 | ---
281 |
282 | **[⬆ Back to Top](#table-of-contents)**
283 |
284 | #### 7. What will be the output after clicking the button?
285 |
286 | ```javascript
287 | import { useState } from 'react';
288 |
289 | export default function App() {
290 | const [items, setItems] = useState([1, 2, 3]);
291 |
292 | function handleClick() {
293 | items.push(4);
294 | setItems(items);
295 | }
296 |
297 | return (
298 | <>
299 | Items: {items.join(', ')}
300 | Add Item
301 | >
302 | );
303 | }
304 | ```
305 |
306 | - 1: Items: 1, 2, 3, 4
307 | - 2: Items: 1, 2, 3
308 | - 3: Error: Cannot mutate state directly
309 | - 4: Items: 1, 2, 3, 4, 4 (duplicates on each click)
310 |
311 | Answer
312 |
313 |
314 | ##### Answer: 2
315 |
316 | React uses `Object.is()` comparison to detect state changes. When you mutate the array directly using `items.push(4)` and then pass the same array reference to `setItems(items)`, React sees it as the same object (same reference) and doesn't trigger a re-render.
317 |
318 | To properly update an array in state, you should create a new array:
319 | ```javascript
320 | setItems([...items, 4]); // or
321 | setItems(items.concat(4));
322 | ```
323 |
324 | This is a common mistake that leads to bugs where the UI doesn't update even though the underlying data has changed. Always treat state as immutable in React.
325 |
326 |
327 |
328 |
329 | ---
330 |
331 | **[⬆ Back to Top](#table-of-contents)**
332 |
333 | #### 8. What is the output after the component mounts?
334 |
335 | ```javascript
336 | import { useState, useEffect } from 'react';
337 |
338 | export default function Counter() {
339 | const [count, setCount] = useState(0);
340 |
341 | useEffect(() => {
342 | setCount(1);
343 | });
344 |
345 | useEffect(() => {
346 | setCount(2);
347 | }, []);
348 |
349 | console.log('Rendered with count:', count);
350 |
351 | return Count: {count}
;
352 | }
353 | ```
354 |
355 | - 1: "Rendered with count: 0", "Rendered with count: 2"
356 | - 2: "Rendered with count: 0", "Rendered with count: 1", "Rendered with count: 2"
357 | - 3: Infinite loop / Maximum update depth exceeded error
358 | - 4: "Rendered with count: 0", "Rendered with count: 2", "Rendered with count: 1"
359 |
360 | Answer
361 |
362 |
363 | ##### Answer: 3
364 |
365 | This code creates an **infinite loop** because the first `useEffect` has no dependency array, which means it runs after **every render**. Here's what happens:
366 |
367 | 1. Initial render with `count: 0`
368 | 2. After render, both effects run (first sets count to 1, second sets count to 2)
369 | 3. State update triggers re-render with `count: 2`
370 | 4. After render, the first effect runs again (no dependencies), setting count to 1
371 | 5. This triggers another re-render, and the cycle continues infinitely
372 |
373 | React will detect this and throw an error: **"Maximum update depth exceeded"**.
374 |
375 | The fix is to add a dependency array to the first useEffect:
376 | ```javascript
377 | useEffect(() => {
378 | setCount(1);
379 | }, []); // Add empty dependency array
380 | ```
381 |
382 |
383 |
384 |
385 | ---
386 |
387 | **[⬆ Back to Top](#table-of-contents)**
388 |
389 | #### 9. What will be displayed on the screen?
390 |
391 | ```javascript
392 | import { useMemo } from 'react';
393 |
394 | export default function App() {
395 | const expensiveCalculation = useMemo(() => {
396 | console.log('Calculating...');
397 | return 100 + 200;
398 | });
399 |
400 | return Result: {expensiveCalculation}
;
401 | }
402 | ```
403 |
404 | - 1: Result: 300
405 | - 2: Result: function() { ... }
406 | - 3: Result: undefined
407 | - 4: Error: useMemo requires a dependency array
408 |
409 | Answer
410 |
411 |
412 | ##### Answer: 4
413 |
414 | The `useMemo` hook **requires** a dependency array as the second argument. Without it, React will throw an error during development:
415 |
416 | ```
417 | Error: useMemo requires a dependency array as the second argument
418 | ```
419 |
420 | The correct usage is:
421 | ```javascript
422 | const expensiveCalculation = useMemo(() => {
423 | console.log('Calculating...');
424 | return 100 + 200;
425 | }, []); // Dependency array is required
426 | ```
427 |
428 | With an empty dependency array `[]`, the calculation runs once during the initial render and the memoized value (300) is reused on subsequent renders. If you include dependencies, the calculation re-runs whenever those dependencies change.
429 |
430 |
431 |
432 |
433 | ---
434 |
435 | **[⬆ Back to Top](#table-of-contents)**
436 |
437 | #### 10. What is the output after clicking the button twice?
438 |
439 | ```javascript
440 | import { useState } from 'react';
441 |
442 | export default function Form() {
443 | const [person, setPerson] = useState({
444 | name: 'John',
445 | age: 30
446 | });
447 |
448 | function handleClick() {
449 | person.age = person.age + 1;
450 | setPerson(person);
451 | }
452 |
453 | return (
454 | <>
455 | Age: {person.age}
456 | Increment Age
457 | >
458 | );
459 | }
460 | ```
461 |
462 | - 1: Age: 32
463 | - 2: Age: 31
464 | - 3: Age: 30
465 | - 4: Error: Cannot mutate state
466 |
467 | Answer
468 |
469 |
470 | ##### Answer: 3
471 |
472 | Similar to the array mutation issue, directly mutating an object's property and then passing the same object reference to the state setter doesn't trigger a re-render. React uses `Object.is()` to compare the old and new state values. Since `person` is still the same object reference, React doesn't detect any change.
473 |
474 | Even though `person.age` is being incremented internally, the UI shows the initial value (30) because React never re-renders the component.
475 |
476 | The correct approach is to create a new object:
477 | ```javascript
478 | function handleClick() {
479 | setPerson({
480 | ...person,
481 | age: person.age + 1
482 | });
483 | }
484 | ```
485 |
486 | This creates a new object with a new reference, which React recognizes as a state change and triggers a re-render. This is a fundamental principle in React: **always treat state as immutable**.
487 |
488 |
489 |
490 |
491 | ---
492 |
493 | **[⬆ Back to Top](#table-of-contents)**
494 |
495 | #### 11. What will be logged to the console?
496 |
497 | ```javascript
498 | import { useEffect, useState } from 'react';
499 |
500 | export default function App() {
501 | const [count, setCount] = useState(0);
502 |
503 | useEffect(() => {
504 | console.log('Effect A');
505 | return () => console.log('Cleanup A');
506 | }, [count]);
507 |
508 | useEffect(() => {
509 | console.log('Effect B');
510 | return () => console.log('Cleanup B');
511 | }, []);
512 |
513 | console.log('Render');
514 |
515 | return setCount(count + 1)}>Count: {count} ;
516 | }
517 | ```
518 |
519 | **On initial mount:**
520 | - 1: "Render", "Effect A", "Effect B"
521 | - 2: "Effect A", "Effect B", "Render"
522 | - 3: "Render", "Effect B", "Effect A"
523 | - 4: "Effect A", "Cleanup A", "Effect B", "Render"
524 |
525 | **After first button click:**
526 | - 1: "Render", "Cleanup A", "Effect A"
527 | - 2: "Cleanup A", "Render", "Effect A"
528 | - 3: "Render", "Effect A", "Cleanup A"
529 | - 4: "Render", "Cleanup B", "Effect B", "Cleanup A", "Effect A"
530 |
531 | Answer
532 |
533 |
534 | ##### Answer for initial mount: 1, Answer for first click: 1
535 |
536 | **On initial mount:**
537 | 1. Component renders first: "Render"
538 | 2. After render, effects run in the order they're defined: "Effect A", "Effect B"
539 | 3. Cleanup functions are registered but not executed
540 |
541 | **After first button click:**
542 | 1. State changes, component re-renders: "Render"
543 | 2. Before running Effect A again (because `count` dependency changed), its cleanup runs: "Cleanup A"
544 | 3. Effect A runs again: "Effect A"
545 | 4. Effect B doesn't run because its dependency array `[]` is empty (no changes)
546 | 5. Cleanup B is not called because Effect B doesn't re-run
547 |
548 | **Key takeaways:**
549 | - Render happens first, then effects run
550 | - Effects run in the order they're defined
551 | - Cleanup runs before re-running an effect (when dependencies change)
552 | - Effects with empty dependency arrays only run once on mount
553 |
554 |
555 |
556 |
557 | ---
558 |
559 | **[⬆ Back to Top](#table-of-contents)**
560 |
--------------------------------------------------------------------------------