Learning React from official docs and various other sources.
- JavaScript Basics (Read it!)
- 10 React Hooks Explained
- React Router in 45 minutes
- SSG vs CSR vs SSR vs ISR
- Visual Explanation of CSR, SSR, SSG and ISR
- Understanding CSR, SSR, SSG, and ISR
Read this as this is an important concept.
A simple react app looks like this
some-project
├── public
│ └── index.html
├── src
│ ├── App.js
│ ├── index.js
│ └── styles.css
├── package.json
├── README.md
└── .gitignore
The files and the output look like this
index.html |
index.js |
---|---|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- It serves as a container for your React app -->
<div id="root"></div>
</body>
</html> This is what gets sent to the browser along with Even though there is no direct reference to |
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";
import App from "./App";
// This creates a root node in the React DOM.
const root = createRoot(document.getElementById("root"));
// When .render is called, App component is rendered into this root node of the React DOM.
// The React DOM is reconciled with the actual DOM by the React library,
// effectively "mounting" the App component to the real DOM.
root.render(
<StrictMode>
<App />
</StrictMode>
); Read the comments in this code above. |
Functions starting with use are called Hooks. useState
is a built-in Hook provided by React.
Hooks are more restrictive than other functions. You can only call Hooks at the top of your components (or other Hooks). If you want to use useState
in a condition or a loop, extract a new component and put it there.
For eg:
function MyComponent(props) {
if (props.condition) {
const [value, setValue] = useState(0); // ❌ Invalid Hook call
}
// ...
}
The proper way:
function MyComponent(props) {
if (props.condition) {
return <MySubComponent />;
}
// ...
}
function MySubComponent() {
const [value, setValue] = useState(0); // ✅ Valid Hook call
// ...
}
Read the comments below to see how the code works.
import { useState } from "react";
export default function MyApp() {
console.log("MyApp rendered.");
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<h1>Counters that update together</h1>
<MyButton count={count} onClick={handleClick} />
<MyButton count={count} onClick={handleClick} />
</div>
);
}
// {} in '{ count, onClick }' is because we're destructuring/ unpacking "props" object passed to it into distinct variables
// In JS, primitive values (like numbers, strings, booleans) are passed by value while objects
// (including arrays and functions) are passed by reference.
// Here 'count' is passed by value but 'onClick' is passed by reference.
// When this button is clicked, handleClick function in MyApp component is called which updates 'count' state
// in MyApp component. Since 'count' is part of MyApp component's state, changing it will trigger re-render of
// MyApp as well as child components that depend on that state (MyButton in this case).
function MyButton({ count, onClick }) {
console.log("MyButton rendered.");
return <button onClick={onClick}>Clicked {count} times</button>;
}
Specificity is a concept in CSS that determines which styles will be applied to an element when there are conflicting styles. The specificity is calculated as a three-digit number (A, B, C):
- 'A' refers to the count of inline styles (styles applied directly to the HTML element using the style attribute).
- 'B' refers to the count of ID selectors.
- 'C' refers to the count of class selectors, attribute selectors, and pseudo-classes.
A > B > C
For example
<!-- Inline style (A=1,B=0,C=0) will take precedence -->
<div id="some-id" class="some-class" style="color: blue;">
This text will be blue.
</div>
<style>
/* ID Selector (A=0,B=1,C=0) has higher specificity than class */
#some-id { color: red; }
/* Class Selector (A=0,B=0,C=1) has the least specificity */
.some-class { color: green; }
</style>
-
Download the code from the link referenced above
-
Clone this repo down to your local, and paste the code downloaded from previous step into it
-
Launch your IDE (I'm using Rider here) and open this folder
-
Open integrated terminal in your IDE and navigate into this folder
cd ./tic-tac-toe/
-
Install dependencies
npm install
-
Add
node_modules
folder to .gitignore.
Right clicknode_modules
folder -> Git -> Add to.gitignore
->.gitignore
-
Add
package-lock.json
file to source control.
Right clickpackage-lock.json
file -> Git -> Add -
Start local server
npm start
The script that gets called is this https://github.com/akhanalcs/react/blob/f1943884e245f862ed69c4c22cbfa5d426321860/tic-tac-toe/package.json#L6
When the script was run, I got this message
I said 'Y' and it was able to run the app and launch a new browser window (in Edge). The config it added to
package.json
isreactjs/tic-tac-toe/package.json
Lines 17 to 28 in 48185e2
The config basically means which versions of which browsers to support with transpiled output.
In the "production" context, the following definitions apply:
- ">0.2%": This targets browsers with greater than 0.2% usage globally.
- "not dead": This targets browsers that are not "dead" (i.e., no longer updated by their respective organizations).
- "not op_mini all": This excludes the Opera Mini browser from the targeted browsers due to its certain limitations.
In the "development" context, the following definitions apply:
- "last 1 chrome version": This targets the most recent version of Google Chrome.
- "last 1 firefox version": This targets the most recent version of Mozilla Firefox.
- "last 1 safari version": This targets the most recent version of Apple Safari.
React handles re-rendering of lists and updates to the Virtual DOM based on keys.
- If the current list has a key that didn’t exist before, React creates a component.
On re-render, React sees that key "2" didn't exist before. Therefore, it creates a new
// Initial Render const elements = [<li key="1">Element 1</li>]; // Re-render const elements = [<li key="1">Element 1</li>, <li key="2">Element 2</li>];
li
component for "Element 2". - If the current list is missing a key that existed in the previous list, React destroys the previous component.
On re-render, React notes that key "2" from the previous list doesn't exist in the current list. So, it removes the
// Initial Render const elements = [<li key="1">Element 1</li>, <li key="2">Element 2</li>]; // Re-render const elements = [<li key="1">Element 1</li>];
li
component for "Element 2". - If two keys match, the corresponding component is moved.
On re-render, React sees that the keys "1" and "2" still exist but in different positions. So, it "moves" them in the Virtual DOM to match the new order, rather than destroying and recreating them.
// Initial Render const elements = [<li key="1">Element 1</li>, <li key="2">Element 2</li>]; // Re-render const elements = [<li key="2">Element 2</li>, <li key="1">Element 1</li>];
It’s strongly recommended that you assign proper keys whenever you build dynamic lists. If you don’t have an appropriate key, you may want to consider restructuring your data so that you do.
This is excellent. Read it!
I'm using Jetbrains Rider.
Follow this guide.
npm install --save-dev eslint-config-react-app eslint@^8.0.0
Then create a file named .eslintrc.json
with following contents in the root folder of your project:
{
"extends": "react-app"
}
Then integrate ESLint in Rider using this guide.
![image](https://private-user-images.githubusercontent.com/30603497/296517208-ae0778c0-cb31-4c57-9c54-c99ba3f34db7.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTczNjYsIm5iZiI6MTcwOTUxNzA2NiwicGF0aCI6Ii8zMDYwMzQ5Ny8yOTY1MTcyMDgtYWUwNzc4YzAtY2IzMS00YzU3LTljNTQtYzk5YmEzZjM0ZGI3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAxNTEwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWRhZjlhMmI4ZGFjNGRiY2QzZWYzMjg2ZGYwNWMyMWMyM2Q2MWM1MDI4ZjlkNzI2NmY2NjVkZjc1MmQxYzFjMjUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.FMrVD2HRn9B8Iz7fGJWz-TE22mDr95vQmP2BkNdgMZ0)
Note about ^
and ~
in version numbers:
- The caret (
^
) allows changes that do not include the next major version.
For example,^2.3.0
allows changes from2.3.0
up to but not including3.0.0
. - The tilde (
~
) allows changes that do not include the next minor version and major version, thereby only allowing patch-level changes for a given minor version.
For example,~2.3.0
allows changes from2.3.0
up to but not including2.4.0
.
npm install --save-dev --save-exact prettier
Create a file named .prettierignore
at the package.json
level to specify files you want to ignore. For example:
node_modules
Configure Prettier in Rider
![image](https://private-user-images.githubusercontent.com/30603497/296515065-671519b5-c8bc-4708-848a-bde74887f6f8.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTczNjYsIm5iZiI6MTcwOTUxNzA2NiwicGF0aCI6Ii8zMDYwMzQ5Ny8yOTY1MTUwNjUtNjcxNTE5YjUtYzhiYy00NzA4LTg0OGEtYmRlNzQ4ODdmNmY4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAxNTEwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTMwOGQwM2Q0ZGU1ZWJjYzJhODcyOGU1MDcyZDQ5MzJkYjRiMjU1ZjIzNzgzYjJjZmQ5Y2JkNzkwNTYzNmZhZDEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.16KyFCsGjJC17ozL5OQuVnEuTFeERxgGlZsVoU5ws8g)
Turn off all rules that are unnecessary or might conflict with Prettier
npm install --save-dev eslint-config-prettier
And add this to your ESLint configuration. Now your .eslintrc.json
file should look like this
reactjs/tic-tac-toe/.eslintrc.json
Lines 1 to 9 in a4eeae7
Reference (Read this!)
Data in a flux application flows in a single direction.
![image](https://private-user-images.githubusercontent.com/30603497/296808538-8080943d-3818-4558-86a0-b5f3c44deb65.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTczNjYsIm5iZiI6MTcwOTUxNzA2NiwicGF0aCI6Ii8zMDYwMzQ5Ny8yOTY4MDg1MzgtODA4MDk0M2QtMzgxOC00NTU4LTg2YTAtYjVmM2M0NGRlYjY1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAxNTEwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTNmYzRhOTMzZjE3ZjEzNjg3MjgxZjUwOWQ5N2QwMjRhNTQ3MzQ2MGYyZjViMTNhMzU0MDljZjQyZTA0ZDE4YWMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.WJMaJ3eFTte80p0tUQ_kEwBhP8tBXVyCZARo2vVWyWs)
Explanation with a simple example
flux-example
├── public
│ └── index.html
├── src
│ ├── containers
│ │ └── AppContainer.js // 👈 Controller View
│ ├── data
│ │ ├── CounterActions.js // 👈 Action creator
│ │ ├── CounterActionTypes.js // 👈 Just holds some constants. Nothing special here
│ │ ├── CounterStore.js // 👈 Store
│ │ └── Dispatcher.js // 👈 Singleton dispatcher per project
│ ├── App.js
│ ├── index.js
│ └── styles.css
└── package.json
![image](https://private-user-images.githubusercontent.com/30603497/296851741-47b9b72b-f9b4-4b0d-95ea-1d5b05615cee.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTczNjYsIm5iZiI6MTcwOTUxNzA2NiwicGF0aCI6Ii8zMDYwMzQ5Ny8yOTY4NTE3NDEtNDdiOWI3MmItZjliNC00YjBkLTk1ZWEtMWQ1YjA1NjE1Y2VlLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAxNTEwNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTg2ZjVmYmRkNGZiNGRhNTQ2NTgxYzc5NjlkODk2NDU1MTcwZTc0MGRhNmNjMDVkZTA0OTAxOWRiNDc1NDAxNTQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.YfZL7NpMjpEggeBZx7ejylFzH-VyR4y2u5pvyBAn0eY)
-
Actions are simple objects containing the new data and an identifying type property. For eg:
{ type: 'ADD', value: count }
-
Action creators are helper methods that create an action and dispatch it to the dispatcher. For eg:
reactjs/flux-example/src/data/CounterActions.js
Lines 5 to 10 in 479bd29
-
All data flows through the dispatcher as a central hub. Actions are provided to dispatcher in an action creator method and most often originate from user interactions with the views. It is essentially a registry of callbacks into the stores and has no real intelligence of its own — it is a simple mechanism for distributing the actions to the stores. For eg:
reactjs/flux-example/src/data/Dispatcher.js
Lines 1 to 3 in 479bd29
Each store registers itself and provides a callback. For eg:
reactjs/flux-example/src/data/CounterStore.js
Lines 5 to 27 in 479bd29
-
Stores contain the application state and logic. They register themselves with the dispatcher upon creation. For eg:
reactjs/flux-example/src/data/CounterStore.js
Lines 5 to 9 in 479bd29
Stores emit a 'change' event when their state changes. -
Controller views are React components that listen for those 'change' events, retrieve new data from the stores, and re-render. For eg:
reactjs/flux-example/src/containers/AppContainer.js
Lines 5 to 21 in 479bd29
-
Views are typically dumb components that take the data passed in via props and render UI. For eg:
reactjs/flux-example/src/App.js
Lines 1 to 13 in 479bd29
- Used for delaying the computation of result until it's needed (lazy evaluation).
- Controlling the order of asynchronous operations.
- Wrapping computations to be invoked later.
The below example lets you start a task right away when called and grab the result at a later time. It also lets you grab the results of multiple tasks in a particular order even if, for example, the first task takes longer than the second one.
// It kicks off an asynchronous task that takes 'effort' milliseconds
function DoSomething(task, effort) {
console.log(task + " started");
let data, fn; // fn is assigned with callback later
setTimeout(() => {
data = task + " completed";
if (fn) {
fn(data);
}
}, effort);
// This is a THUNK because it defers some work for later
// it can be named, or anonymous
return function (callback) {
if (data) {
// Case 1. Our task has completed by the time the callback is passed to it
// At this point we already have data, so give it to the callback by calling it with 'data'
callback(data);
} else {
// Case 2. Our task hasn't completed when the callback is passed to it
// Assign 'fn' here so that callback will be called with the data when the task completes (inside setTimeout)
fn = callback;
}
};
}
// Both of these calls complete immediately which starts both of the tasks
// At this point, const task1 and const task2 have the return function assigned to them
// Because of 'closure' the inner returned function(function (callback)) will have access to outer scope variables 'data' and 'fn' even after 'DoSomething' has returned
const task1 = DoSomething("task1", 6000); // task1 takes 6 seconds
const task2 = DoSomething("task2", 3000); // task2 takes 3 seconds
// I need results of task1 in task2. Remember: task1 takes longer than task2
// I want both tasks to start together. By the time task1 completes, task2 will also have completed
task1(function (task1data) {
console.log(task1data);
// task2 has already completed by now (since it was shorter)
// so calling task2 with a callback immediately logs the result of task2
task2(function (task2data) {
// At this point, task1data is always defined
if (task1data) {
console.log(task2data);
}
});
});
// The output is:
// task1 started
// task2 started
// task1 completed
// task2 completed
For more info, read this and watch this.
Redux is a pattern and library for managing and updating application state, using events called "actions". It serves as a centralized store for state that needs to be used across your entire application with rules ensuring that the state can only be updated in a predictable fashion.
- Add Redux to your project
npm install @reduxjs/toolkit react-redux
- Create a redux store using a root reducer function.
- Provide the Redux store to React
- Create a Redux state slice
- Add slice reducers to the store
- Use Redux state and actions in React components
-
Action
An action is a plain JS object that has a type field. You can think of an action as an event that describes something that happened in the app. The type field is written like
"domain/eventName"
. Action object can have other fields with additional information which is put in a field calledpayload
. For eg:const addCounterAction = { type: 'counter/add', payload: 5 }
-
Action Creators
Is a function that creates and returns an action object. For eg:
const increment = () => { return { type: 'counter/increment' } }
-
Reducers
Is a function that receives
state
andaction
object, decides how to update the state if necessary and returns the new state. Think of a reducer as an event listener which handles events based on the received action (event) type. They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.// IMPORTANT: Parameters of a reducer function are (previousResult, currentItem) function counterReducer(state = initialState, action) { // Check to see if the reducer cares about this action if (action.type === 'counter/increment') { // If so, make a copy of `state` return { ...state, // and update the copy with the new value value: state.value + 1 } } // otherwise return the existing state unchanged return state }
Usage of reducer in
Array.reduce()
methodconst actions = [ { type: 'counter/increment' }, { type: 'counter/increment' } ]; const initialState = { value: 0 }; const finalResult = actions.reduce(counterReducer, initialState); console.log(finalResult); // { value: 2 }
-
Store
Redux application state lives in an object called the store. The store is created by passing in a reducer and has a method called
getState()
that returns the current state value.import { configureStore } from '@reduxjs/toolkit' const store = configureStore({ reducer: counterReducer }) console.log(store.getState()) // {value: 0}
-
Dispatch
The redux store has a method called
dispatch
. The only way to update the state is to callstore.dispatch()
and pass in an action object. The store will run its reducer function and save the new state value inside and we can callgetState()
to retrieve the updated value.store.dispatch({ type: 'counter/increment' }) console.log(store.getState()) // {value: 1}
You can think of dispatching actions as "triggering an event" in the application. Something happened, and we want the store to know about it. Reducers act like event listeners, and when they hear an action they are interested in, they update the state in response.
We typically call action creators to dispatch the right action:
const increment = () => { return { type: 'counter/increment' } } store.dispatch(increment()) console.log(store.getState()) // {value: 2}
-
Selectors Selectors are functions that know how to extract specific pieces of information from a store state value. As an application grows bigger, this can help avoid repeating logic as different parts of the app need to read the same data:
const selectCounterValue = state => state.value const currentValue = selectCounterValue(store.getState()) console.log(currentValue) // 2
- Redux dispatches a special action when the store is created.
- Your overall
counterSlice.reducer
sees this action. - The
counterSlice.reducer
returns the initialState you defined. - Redux uses this returned
initialState
as the initial state for your store.
Step 1 | Step 2 |
---|---|
![]() A bank teller clicks 'Deposit' button in the UI. |
![]() An action creator will create an action object which is given to the Dispatcher. Dispatcher then sends it to the Redux store. |
Step 5 |
---|
![]() Each component that sees its data has changed forces a re-render with the new data, so it can update what's shown on the screen. |
A thunk is a specific kind of Redux function that can contain asynchronous logic. Thunks are written using 2 functions:
- Inside thunk function which gets
dispatch
andgetState
as arguments - The outside creator function which creates and returns the thunk function.
Simple example
// thunk action creator
// allows us to do async logic
// can be dispatched like regular action. For eg: dispatch(incrementAsync(1)
// the outside "thunk creator" function
export const incrementAsync = (amount) => {
// the inside "thunk function" that takes 'dispatch' as argument
// used to delay the dispatch of 'add()` action
// this returned function is called by redux-thunk middleware
// For eg: when redux-thunk middleware sees the result from 'incrementAsync(1)',
// it says "Oh, this is a function, let me call it for you!", and provides it with
// the dispatch and getState arguments (which are bound to the current store).
return (dispatch) => {
setTimeout(() => {
// After the timeout is done, normal Redux action is dispatched to the store
dispatch(add(amount));
}, 1000);
};
};
Example with an API call
// the outside "thunk creator" function
const fetchUserById = userId => {
// the inside "thunk function"
return async (dispatch, getState) => {
try {
// make an async call in the thunk
const user = await userAPI.fetchById(userId)
// dispatch an action when we get the response back
dispatch(userLoaded(user))
} catch (err) {
// If something went wrong, handle it here
}
}
}
Remember that we're not allowed to put any kind of async logic in reducers.
const thunkMiddleware =
({ dispatch, getState }) =>
next =>
action => {
if (typeof action === 'function') {
return action(dispatch, getState)
}
return next(action)
}
-
When the store is created, the middleware is setup
const thunk = thunkMiddleware({dispatch: store.dispatch, getState: store.getState});
thunk
gets called every time an action is dispatched. -
When an action is dispatched (for example,
store.dispatch(incrementAsync())
), Redux internally calls thethunk
function with the next dispatch function, which belongs to the next middleware in the chain or to the store, if there are no more middlewares. For simplicity, let's assume there are no more middlewares, sonext
would be thestore.dispatch
.const actionHandler = thunk(store.dispatch);
This
actionHandler
function is not reused; a new one is created for every dispatched action. -
Redux calls this
actionHandler
with the dispatched actionactionHandler(incrementAsync());
Inside the middleware, the
if (typeof action === 'function')
block gets executed andaction(dispatch, getState)
is called, which calls your thunk action with the dispatch and getState.This sets the timeout and after 1 second, we go back to step 2 and at step 3, we get to
return next(action)
which just passes the action to the store's dispatch function, which in turn activates the reducer with that action.
Finish reading the 'Essentials' section of Redux docs
Essentials vs Fundamentals difference
-
Add type definitions
npm install @types/react @types/react-dom --save-dev
-
Add
tsconfig.json
{ "compilerOptions": { "target": "ES2022", "module": "ES2022", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, // This makes noImplicitAny as true "lib": [ "ES2022", "dom" ], "jsx": "preserve" } }
-
Change filename extensions. If you have JSX in your file, change its extension to
.tsx
otherwise change it to.ts
. For eg:- Change
App.js
toApp.tsx
- Change
index.js
toindex.tsx
- Change
-
Fix error in
index.tsx
For eg: use non-null assertion operator
const rootElement = document.getElementById("root")!;
interface MyInterface {
name: string;
age: number;
}
let obj: MyInterface = { name: 'John', age: 25 };
// Access the value of a property
let ageValue: number = obj['age'];
console.log(ageValue);
// Access the type of a property (Indexed Access Type example)
type AgeType = MyInterface['age']; // Type of 'AgeType' is number
import React from "react"
// React.ReactNode accepts the most inputs
interface ReactNodeProps {
children: React.ReactNode;
}
const RNode = (props: ReactNodeProps) => <div>{props.children}</div>
const ReactNodeApp = () => <>
<RNode><p>One element</p></RNode>
<RNode>
<>
<p>Fragments for</p>
<p>More elements</p>
</>
</RNode>
<RNode>1</RNode>
<RNode>Hello</RNode>
<RNode>{null}</RNode>
<RNode>{true}</RNode>
// Must have children though
// Error: Property 'children' is missing in type '{}' but required in type 'ReactNodeProps'.
<RNode />
</>
RNode
is a React component that takes props
as an argument. children
prop is a special prop in React which is used to pass components as data to other components.
<RNode><p>One element</p></RNode>
is an instance of usage of the RNode
component with a single child which is <p>One element</p>
element.
The output HTML would be <div><p>One element</p></div>
.
Finish reading the 'Using TypeScript' section of React docs
This is also a great page: https://react-typescript-cheatsheet.netlify.app/
Reference(Read this!)
useRef
lets you reference a value that's not needed for rendering.
By using a ref, you ensure that
- You can store information between re-renders unlike regular variables which reset on every render.
- Changing it does not trigger a re-render unlike state variables which trigger a re-render.
- The information is local to each copy of component unlike the variables outside which are shared.
In example below intervalIdRef
is essentially used as a mutable instance variable that exists over the lifecycle of the component.
It does not cause a rerender when its value changes and it survives between component renders so that we can use it to stop setInterval()
.
reactjs/hooks-examples/src/components/Stopwatch.tsx
Lines 1 to 50 in 24ba803
useEffect
lets you synchronize a component with an external system. This includes dealing with network, browser DOM, animations, widgets written using a different UI library, and other non-React code.
Effects let you specify side effects caused by the rendering itself, rather than by a particular event.
For eg: Consider a ChatRoom component that must connect to the chat server whenever it’s visible on the screen. Connecting to a server is not a pure calculation (it’s a side effect) so it can’t happen during rendering. However, there is no single particular event like a click that causes ChatRoom to be displayed.
For eg:
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
The second argument: [serverUrl, roomId]
is the dependency array of the useEffect
hook.
- If you pass an empty array [] as the second argument to
useEffect
, it means "run this effect once after the initial render, and do not run it after any subsequent renders." - If you pass an array with variables like
[serverUrl, roomId]
, it means "run this effect after the initial render, and also run it after any subsequent render if the values ofserverUrl
orroomId
have changed since the last render." - If you omit the second argument, it means "run this effect after every render."
useMemo
lets you cache the result of an expensive calculation.
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
The useMemo
hook takes two parameters: a function and a dependency array. The function you pass as the first parameter generates the value to be stored, and the array you pass as the second parameter tells React when to update that value.
It only re-runs filterTodos(todos, tab)
if todos
or tab
changes.
If neither todos
nor tab
changes between renders, useMemo
just returns the most recent memoized value without running filterTodos
function again.
useCallback
lets you cache a function definition between re-renders.
In JavaScript, functions are objects, and creating a function is an operation that consumes some resources.
Consider this simple counter example
function Counter() {
const [count, setCount] = useState(0);
function incrementCount() {
setCount(count + 1);
}
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment</button>
</div>
);
}
Here a new function (i.e., a new object) is created each time the Counter
component is rendered. Typically, this might not be an issue, but if the Counter
component re-renders frequently or incrementCount
is passed as a prop to child components, this could potentially lead to performance issues.
This is where useCallback
comes in handy. With useCallback
, React can "memoize" the function — the incrementCount function is not recreated on every render, but only when a value in the dependency array changes.
For eg:
function Counter() {
const [count, setCount] = useState(0);
const incrementCount = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<ExpensiveChildComponent onEventHappens={incrementCount} />
</div>
);
}
If Counter
was re-rendering frequently but count
wasn't changing, then incrementCount
would remain the same between renders, but without useCallback
, a new incrementCount
function would be created every render, potentially causing unnecessary renders of ExpensiveChildComponent
that depend on the incrementCount
prop.
Context lets a component receive information from distant parents without passing it as props. For example, your app’s top-level component can pass the current UI theme to all components below, no matter how deep.
Here we specify what the context will contain.
reactjs/hooks-examples/src/contexts/ThemeContext.ts
Lines 1 to 7 in aaf01ce
This makes the provided value available to <MyComponent />
and all of its children components.
reactjs/hooks-examples/src/App.tsx
Lines 1 to 17 in aaf01ce
Here the value of theme will be whatever value is currently provided via ThemeContext.Provider in App component.
Use it using useContext
.
reactjs/hooks-examples/src/MyComponent.tsx
Lines 1 to 11 in aaf01ce
Finish reading the 'Learn React' section of React docs
If you made it this far, this section isn't strictly necessary to learn at this time, but read it and practice it when you get time. The docs are excellent.