useLayoutEffect
BrowserRouter
Route
Routes
Link
React library makes use of numerous modern JavaScript capabilities, such as array or object destructing and rest/spread syntax etc.
Knowing all these new javascript capabilities will make us better React developers and also help us distinction between what’s React library adding it to our code and what comes with JavaScript already.
JavaScript has lexical (also called static or physical) scoping and closures. This means you can tell the scope of an identifier by looking at the source code.
The four scopes are:
Pair of curly braces defines a block in JavaScript,
{
}
scene-1,
{
let nameLet = "nameLet"; // Block Scope
var nameVar = "nameVar"; // Global Scope
}
var globyVar = "globyVar"; // Global Scope
let globyLet = "globyLet"; // Global Scope
console.log(nameVar); // nameVar
console.log(nameLet); // ReferenceError: nameLet is not defined
console.log(globyVar); // globyVar
console.log(globyLet); // globyLet
scene-2,
if (true) {
var nameInSideIfVar = "nameInSideIfVar"; // Global Scope
let nameInSideIfLet = "nameInSideIfLet"; // Block Scope
}
console.log(nameInSideIfVar); // nameInSideIfVar
console.log(nameInSideIfLet); // ReferenceError: nameInSideIfLet is not defined
scene-3,
for (var i = 1; i <= 10; i++) {
// Global Scope
}
for (let j = 1; j <= 10; j++) {
// Block Scope
}
console.log(i); // 11
console.log(j); // ReferenceError: j is not defined
scene-4,
function sum(a, b) {
// Function Scope, so available only to nested functions and nested blocks
var resultVar = a + b;
let resultLet = a + b;
}
sum(4 + 3);
console.log(resultLet); // resultLet is not defined
console.log(resultVar); // resultVar is not defined
As for as the scope concerned let and const behaves same way. But they are different when it comes to storing the data.
We use const when the reference assigned to a identifiers is meant to be constant. See, I did’nt say data/value, said reference. References assigned to const identifiers cannot be changed.
// Primitive or Scalar values
const answer = 42;
const greeting = "Hello";
answer = 100; // TypeError: Assignment to constant variable
// Objects
const numbers = [2, 4, 6];
const person = {
firstName: "Brendan",
lastName: "Eich",
};
numbers.push(10);
person.firstName = "Avinash";
console.log(numbers); // [ 2, 4, 6, 10 ]
console.log(person.firstName); // Avinash
Change the const with let in above code, everything works as is. Because references assigned to let identifiers can be changed.
Which one to use and when?
const todosCount = 42;
/*
A lot of code here...
*/
todosCount; // is still 42;
// vs
let todosCount = 42;
/*
A lot of code here...
*/
todosCount; // might have changed;
Let’s consider the following JavaScript statement:
const user = {
name: "Avinash",
};
if (user.name) {
console.log("This user has a name!");
}
We have a user object, and we want to run a console.log depending on a condition. The condition is the JavaScript expression user.name
.
But user.name
isn’t a boolean value. The user’s name is neither true
nor false
.
Q. How above program still valid and works?
A. In JavaScript, every value is either “truthy” or “falsy”. A truthy value is one that counts as true when it comes to conditions.
Most values in JavaScript are truthy. But only few are falsy, so, list all of the falsy values:
false
null
undefined
'' (empty string)
0 (and related values, like 0.0 and -0)
NaN
user.name
is a truthy value, because it’s a string with at least 1 character. Every string other than ''
is truthy.
Note:
[]
(empty array) and{}
(empty object) are truthy values, not falsy. This means that every object/array is truthy.
Sometimes, we need convert truthy value into true
, or a falsy value into false
.
How to achieve it?
Using Boolean()
constructor function,
Boolean(4); // true
Boolean(0); // false
Boolean([]); // true
Boolean(""); // false
Boolean("Avinash"); // true
Another way,
!!4; // true
!!0; // false
!![]; // true
!!""; // false
!!"Avinash"; // true
!!
isn’t actually a JavaScript operator; we’re repeating the NOT operator (!
) twice.
Ex,
!true; // false
!false; // true
If we use the !
with a non-boolean value, it will flip a truthy value to false
, or a falsy value to true
:
!4; // false, since 4 is truthy
!0; // true, since 0 is falsy
We can stack multiple !
operators to flip it back and forth:
!4; // false
!!4; // true
!!!4; // false
!!!!4; // true
To break down what’s going on here: Each !
is evaluated, one at a time. It’s as if there were parenthesis around each set, like this:
!(!(!4))
^^ !4 resolves to `false`
!(!false)
^^^^^^ !false resolves to `true`
!true
^^^^^ !true resolves to `false`
When working with React, we’re allowed to put expressions in our JSX, but we’re not allowed to put statements. But why, lets understand the reason behind it.
An expression is a bit of JavaScript code that resolves to a value or evaluates to value or produces a value.
For example, these are all expressions:
5 → produces 5
(1 + 2) * 2 → produces 6
"hi" → produces "hi"
1 * 10 → produces 10
num > 3 → produces either true or false
isDone ? "🙂" : "🙁" → produces an emoji
[1, 2, 3].pop() → produces the number 3
A JavaScript program is made up of statements. Each statement is an instruction to the computer to do a particular thing.
Statements don’t produce a value
Here are some examples of statements in JavaScript:
let hi = 5;
if (hi > 10) {
// More statements here
}
for (let index = 0; index < 10; index++) {
// More statements here
}
throw new Error('Something went wrong');
statements are the syntax/structure that holds our program together, while expressions fill in the details.
For example, declaring a variable has an expression slot:
let hi = /* some expression or lets call it expression slot */;
We can use any of the expressions we saw earlier in that slot:
let y = 5;
let y = (1 + 2) * 2;
let y = "hi";
let y = 1 * 10;
let y = num > 3;
let y = isDone ? "🙂" : "🙁";
let y = [1, 2, 3].pop()
Also, we can notice expressions are assignable to a variable.
Want to know whether a chunk of JS is an expression or a statement? Try to log it out!
console.log(/* Some chunk of JS here */);
If it runs, the code is an expression. If we get an error, it’s a statement (or, possibly, invalid JS).
This works because all function arguments must be expressions. Expressions produce a value, and that value will be passed into the function. Statements don’t produce a value, and so they can’t be used as function arguments.
Template literals are literals delimited with backtick (`), allowing embedded expressions, multiline strings, string interpolation.
String interpolation nothing but to create strings by doing substitution of placeholders
In earlier versions of JavaScript, if we wanted to dynamically create strings, we needed to use the addition operator (+
):
const lastName = 'C';
const fullName = 'Avinash ' + lastName + ' !';
This works fine, but it’s error prone as we tend forget the space, formatting issue with long concatenation.
Modern JavaScript allows us to embed variables and other expressions right inside strings:
const = 'C';
const fullName = `Avinash ${lastName} !`;
Strings created with backticks are known as template strings, the operation know as string interpolation.
We create a dynamic segment within our string by writing ${}
. Anything placed between the squiggly brackets will be evaluated as a JavaScript expression.
(old way)
console.log("string text line 1\n" + "string text line 2");
(new way)
console.log(`string text line 1
string text line 2`);
let a = 5;
let b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
const html = `
<div>
${new Date()}
</div>
`;
console.info(html);
// <div>
// Thu Dec 23 2021 07:29:09 GMT+0530 (India Standard Time)
// </div>
The below syntax is known as Function declaration
function add(x, y) {
return x + y;
}
If we assign the function declaration to a variable, then it is called Function expression
const add = function (x, y) {
return x + y;
};
So, In JavaScript, functions are usually have been written using the function
keyword
function welcome(name) {
return "Welcome " + name;
}
In ES2015 version, JavaScript received an alternative syntax for creating functions: arrow functions.They look like this:
const welcome = (name) => "Welcome " + name;
This is also Arrow function expressions as we assign it to a variable.
Now, let’s dig into it,
There are two types of arrow functions: short-form and long-form.
short form:
const add = n => n + 1;
long form:
const add = n => {
return n + 1;
}
- The short-form function’s body must be a single expression and It has implicit return.
- The long-form function’s body can contain a number of statements.
- Arrows functions are anonymous.
Optional parentheses
If an arrow function takes a single parameter, the parentheses are optional:
// This is valid:
const logUser = user => {
console.log(user);
}
// This is also valid:
const logUser = (user) => {
console.log(user);
}
The parentheses are mandatory if we have more than 1 parameter:
const logUser = (user, isAdmin) => {
console.log(user, isAdmin);
}
The parentheses are also mandatory if we have no parameters:
const sayHello = () => console.log('Hello!')
Bu why we need arrow functions?
In JavaScript, there are 4 ways you can invoke a regular function.
globalThis => node
window => browser
function myFunction() {
console.log(this);
}
// Simple invocation
myFunction(); // Window
const myObject = {
method() {
console.log(this);
},
};
// Method invocation
myObject.method(); // logs myObject => {method: ƒ}
function myFunction() {
console.log(this);
}
const myContext = { value: "A" };
myFunction.call(myContext); // logs { value: 'A' }
myFunction.apply(myContext); // logs { value: 'A' }
function MyFunction() {
console.log(this);
}
new MyFunction(); // logs an instance of MyFunction => MyFunction {}
arguments
inside it.ex,
const myObject = {
myMethod(items) {
console.log(this); // logs myObject
const callback = () => {
console.log(this); // logs myObject
};
items.forEach(callback);
},
};
myObject.myMethod([1, 2, 3]);
// { myMethod: [Function: myMethod] }
// { myMethod: [Function: myMethod] }
// { myMethod: [Function: myMethod] }
// { myMethod: [Function: myMethod] }
Change the callback to regular function declaration, see the output and why is that out put?
Q. Return an object from arrow function,
const getUser = () => { name: "avinash"};
console.log(getUser()); // undefined
Why it returning undefined
instead of not an object.
A. The pair of curly braces {}
in JavaScript, used for creating object literal and define a scope or block as well, think if
, for
statements for an instance.
In JavaScript, parentheses can be added around any expression, to change its evaluation order.
In order to tell JavaScript engine, that, it’s an object returning not an scope block {}
, enclose it in parenthesis ()
.
const getUser = () => ({ name: "avinash" });
console.log(getUser()); // { name: "avinash"}
const name = "fullName";
const balloon = "🎈";
const ModernObj = {
x: 10,
y: 20,
f0: function () {},
f1() {},
f2: () => {},
[name]: "avinash",
balloon, // Shorthand property names
};
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack/extract values from arrays, or properties from objects, into distinct variables.
const user = {
id: 42,
isVerified: true,
};
const { id, isVerified } = user;
// This is equivalent for above syntax
const id = user.id;
const isVerified = user.isVerified;
const { PI, E, SQRT2 } = Math;
const { readFile } = require("fs");
function userId({ id }) {
return id;
}
userId(user);
const user = {
id: 42,
isVerified: true,
};
const { id: userId, isVerified } = user;
console.log(userId); // 42
Q. what happens if we try to destructure a key from an object which isn’t defined?
const user = {
id: 42,
isVerified: true,
};
const { id, isVerified, name} = user;
console.log(name); // undefined
To fix this, we can assign the default value to a prop,
const { id, isVerified, name = "avinash" } = user;
If user.name
is defined, it’ll be assigned to the new name
variable. Otherwise, it’ll be set to the string avinash
.
Normal way,
function isValidUser(user) {
if (typeof user.name !== "string") {
return false;
}
if (user.password.length < 12) {
return false;
}
return true;
}
Destructing the parameter, we could destructure these values at the top of the function:
function isValidUser(user) {
const { name, password } = user;
if (typeof name !== "string") {
return false;
}
if (password.length < 12) {
return false;
}
return true;
}
Using parameter destructuring, we can do this destructuring right in the function parameters:
function isValidUser({ name, password }) {
if (typeof name !== "string") {
return false;
}
if (password.length < 12) {
return false;
}
return true;
}
All 3 of these code snippets are equivalent
Default parameter values:
Like with typical object destructuring, we can supply default values to be used for parameters.
function sendApiRequest({ method = "GET", numOfRetries }) {}
// When I call this function, I can supply my own value for method:
sendApiRequest({ method: "PUT", numOfRetries: 4 });
// if I want it to be equal to GET, I can omit it entirely:
sendApiRequest({ numOfRetries: 4 });
Let’s say, we have some data that lives in an array, and we want to pluck it out and assign it to a variable.
This has been done by accessing an item by index, and assigning it a value with a typical assignment statement:
const fruits = ["apple", "banana", "cantaloupe"];
const firstFruit = fruits[0];
const secondFruit = fruits[1];
Destructuring assignment offers us a nicer way to accomplish this task:
const fruits = ["apple", "banana", "cantaloupe"];
const [firstFruit, secondFruit] = fruits; // grab first two items
const [, secondFruit] = fruits; // grab the second item, and not the first
const foo = ["one", "two", "three"];
const [red, yellow, green] = foo;
let a, b;
[a, b] = [1, 2];
const foo = ["one", "two"];
const [red, yellow, green, blue] = foo;
// green and blue will be undefined
function f() {
return [1, 2];
}
let a, b;
[a, b] = f();
// Selecting few from array
const [a, , b] = [1, 2, 3];
// a = 1
// b = 3
This collects multiple elements and “condenses” them into a single element.
With Array
const [first, ...restOfthem] = [1, 2, 3];
console.log(first); // 1
console.log(restOfthem); // [ 2, 3 ]
With Object
const data = {
temp1: "001",
temp1: "007",
firstName: "James",
lastName: "Bond",
};
const { temp1, temp2, ...person } = data;
console.log(temp1, temp1, person);
// 002 002 { firstName: 'James', lastName: 'Bond' }
Spread syntax (…) allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
It does opposite of Rest operation
With Array
// This creates brand new array by copying the restOfthem array items
const newArray = [...restOfItems];
//-----------------------
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
With Object
// This creates brand new object by copying the person objects properties
const newObject = {
...person,
};
Classes are a template for creating objects. They encapsulate data with code (method) to work on that data.
JavaScript classes are syntactic sugar over functions.
JavaScript class mechanism are not equivalent to Java, C# or Python class.
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello ${this.name}!`);
}
}
const o1 = new Person("Bond");
o1.greet(); // Hello Bond!
With Inheritance
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello ${this.name}!`);
}
}
class Student extends Person {
constructor(name, level) {
super(name);
this.level = level;
}
greet() {
console.log(`Hello ${this.name} from ${this.level}`);
}
}
const o1 = new Person("Bond");
const o2 = new Student("Bond Jr. 1", "1st Grade");
const o3 = new Student("Bond Jr. 1", "2nd Grade");
o3.greet = () => console.log("I am special!");
o1.greet(); // Hello Bond!
o2.greet(); // Hello Bond Jr. 1 from 1st Grade
o3.greet(); // I am special!
By default, JavaScript synchronous.
In JavaScript, we can code async tasks in 3 ways.
When an async operation had been completed, a callback function (meaning call me back when the operation has been completed) is executed.
A function that Node or Browser will “call back” at a later point in the time of a program.
Ex,
const callbackFunction = (result = {
// Called when the operation completes
});
asyncOperation(params, callbackFunction);
But as soon as you handle multiple async operations, the callback functions nest into each other ending in callback hell.
A promise is a placeholder object for the results of an async task. With the use of promises, we can handle the async operations easier.
const promise = asyncOperation(params);
promise.then((result) => {
// Called when the operation completes
});
Problem is chains of promises.then().then()…then()…
The async/await syntax (starting ES2017). It lets us write async code in a concise and sync manner
An async function is a function declared with the async
keyword, and the await
keyword is permitted within them. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.
(async function () {
const result = await asyncOperation(params);
// Called when the operation completes
})();
Note: async/await is syntactic sugar on top of promises.
ex,
index.html
// promise with then and catch pattern
<script>
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => console.log(json));
</script>
<script>
const getData = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await res.json();
console.log(data);
};
getData();
</script>
OR inside the module, we don’t need the async fn also, like below.
index.html
<script type="module" src="./index.js"></script>
index.js
const getData = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await res.json();
console.log(data);
};
getData();
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await res.json();
console.log(data);
// Both are identical
Challenge, what is the result of the consoles and why?
const getData = async () => {
return 1;
};
console.log(getData()); // Promise {<fulfilled>: 1}
console.log(await getData()); // 1
JavaScript had no built-in module system in earlier version of it.
In the early days, we wrote our JavaScript programs in .js
files, and loaded them all up via <script>
tags in our HTML files. This worked alright, but it meant that every script shared the same environment; variables declared in one file could be accessed in another. It was a bit of a mess.
As part of ECMA2015, JavaScript got its native module system.
When we work with a JS module system, every file becomes a module. A module is a JavaScript file that can contain one or more exports. We can pull the code from one module into another using the import statement.
If we don’t export a piece of data from one module, it won’t be available in any other modules. The only way to share anything between modules is through import/export.
The export statement is used when creating JavaScript modules to export functions, objects, or primitive values from the module so they can be used by other programs with the import statement.
There are two types of exports:
Each file can define one or more named exports:
// prodEnv.js
export const jwtKey = "my-key";
// index.js
import { jwtKey } from "./prodEnv";
export
keyword to make a piece of data, jwtKey
, available to other files.{}
../prodEnv
, is the path to the module. We’re allowed to omit the .js
suffix, since it’s implied..
, refers to the same directory. Two dots, ..
, refers to a parent directory.When it comes to default exports, we always export an expression:
ex-1,
// ✅ Correct:
const hi = 5;
export default hi;
// 🚫 Incorrect
export default const hi = 10;
Ex-2
// prodEnv.js
const config = {
key: "my-key",
port: 8000,
};
export default config;
// index.js
import configObj from "./prodEnv.js";
const { key, port } = configObj;
{}
.// config.js
const envType = 'DEV';
const isUAT = 'YES';
const environment = {
key: 'my-key',
port: 8000
};
// it's called, export list
export {
envType,
isUAT
};
export default environment;
// index.js
import { envType }, environment from './config.js';
// config.js
const key = "my-key";
export { key as authKey };
// index.js
import { authKey } from "./config.js";
as
keyword to rename an export.Sometimes, we’ll run into naming collisions with named imports:
// config.js
export const key = "my-key";
// index.js
import { key as authKey } from "./config.js";
as
keyword to rename an import.index.html
<script type="module" src="./index.js"></script>
config.js
export const envType = "DEV";
const config = {
key: "my-key",
port: 8000,
};
export default config;
index.js
import * as env from "./config.js";
console.log(env);
// {
// "default": {
// "key": "my-key",
// "port": 8000
// },
// "envType": "DEV"
// }
*
to import everything a module exports.Module(file) extensions are mandatory in plain ES Modules.
In Node.js or JS with few build tools, it is optional to mention file extensions. Every module can have multiple named exports, but only a single default export.
arguments is an Array-like local object accessible inside functions that contains the values of the arguments passed to that function.
Not available in arrow function
function add() {
console.log(arguments);
}
add(1, 2, 3);
// [Arguments] { '0': 1, '1': 2, '2': 3 }
An IIFE (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it is defined.
Ex,
// Normal function declaration
function logMessage(message) {
console.log(message);
}
// Normal function invocation
logMessage("Hi, Normy"); //
// IIFE declaring & invocation
(function logMessage(message) {
const subText = "!!🎉🎉";
console.log(message + subText);
})("Hi, IIFE");
But, why?
Avoids global variable namespace conflicts.
Ex,
Let’s say we have 3 script/libs loading and has same variables in all.
index.html
<script>
var a = 10;
setTimeout(() => console.log(a), 1000); //20
</script>
<script>
var a = 20;
setTimeout(() => console.log(a), 1000); //20
</script>
<!--
20
20
-->
Using IIFE, we can solve this problem
<script>
(function () {
var a = 10;
setTimeout(() => console.log(a), 1000); //10
})();
</script>
<script>
(function () {
var a = 20;
setTimeout(() => console.log(a), 1000); //20
})();
</script>
<!--
10
20
-->
IIFE can use normal, arrow or anonymous function.
IIFE now days are not being used more as ES Modules solves the JS’s global scope issues.
An object that supports all of the operations generally allowed to other objects or types.
So what are these operations? Generally, first-class objects can:
A higher-order function is one that either has a function as a parameter, or returns a function. ex, map filter etc.
Default function parameters allow named parameters to be initialized with default values if no value or undefined is passed.
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5, 2)); // 10
console.log(multiply(5)); // 5
condition ? exprIfTrue : exprIfFalse;
// Normal if-else
let result;
let age = 18;
if (age === 18) {
result = "can vote";
} else {
result = "ya ain't";
}
console.log(result); // can vote
// Ternary
let tResult = age === 18 ? "can vote" : "ya ain't";
console.log(tResult); // can vote
Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.
Main benefit is reusability of actions (behavior/logic), not just values.
function greaterThan(n) {
return (m) => m > n;
}
let greaterThan10 = greaterThan(10);
console.log(greaterThan10(11));
// true
function isPromotable(exp) {
return exp > 10 ? true : false;
}
function promote(exp, isEligible) {
if (isEligible(exp)) {
console.log("Promoted 🎉 !!");
} else {
console.log("Next time");
}
}
promote(8, isPromotable); // Next time
The built-in HOFs are map, filter, forEach etc.
One important difference between React and other UI framework/library is that, others such as Angular, Vue etc uses template/domain language to do the iterations, like ngFor
, v-for
respectively.
But in case of React, along with JSX, it uses built-in array methods such as map
, filter
, forEach
etc.
We use forEach
when we want to perform some sort of action on every item in an array.
const pizzaToppings = ["cheese", "avocado", "halibut", "custard"];
pizzaToppings.forEach((topping) => {
console.log(topping);
});
// cheese
// avocado
// halibut
// custard
The forEach
method accepts a function as its argument. This is commonly known as a callback function.
The term “allback function refers to a function that we pass to another function.
We don’t call this function ourselves; instead, we pass it as an argument to the forEach method. The JavaScript engine will call this function for us, supplying the topping argument as it iterates over the array.
Accessing the index
The callback we pass to forEach
takes a second optional parameter:
const pizzaToppings = ["cheese", "avocado", "halibut", "custard"];
pizzaToppings.forEach((topping, index) => {
console.log(index, topping);
});
// 0 cheese
// 1 avocado
// 2 halibut
// 3 custard
The index is the position in the array of the current item, starting from 0.
In many ways, filter
is very similar to forEach
. It takes a callback function, and that callback function will be called once per item in the array.
Unlike forEach
, however, filter
produces a value. Specifically, it produces a new array which contains a subset of items from the original array.
Ex-1,
const students = [
{ name: "Aisha", grade: 89 },
{ name: "Bruno", grade: 55 },
{ name: "Carlos", grade: 68 },
{ name: "Dacian", grade: 71 },
{ name: "Esther", grade: 40 },
];
const studentsWhoPassed = students.filter((student) => {
return student.grade >= 60;
});
console.log(studentsWhoPassed);
/*
[
{ name: 'Aisha', grade: 89 },
{ name: 'Carlos', grade: 68 },
{ name: 'Dacian', grade: 71 },
]
*/
Typically, our callback function should return a boolean value, either true
or false
. The filter
method calls this function once for every item in the array. If the callback returns true
, this item is included in the new array. Otherwise, it’s excluded.
Ex-2,
const numbers = [5, 12, 15, 31, 40];
const evenNumbers = numbers.filter((num) => {
return num % 2 === 0;
});
console.log(numbers); // Hasn't changed: [5, 12, 15, 31, 40]
console.log(evenNumbers); // [12, 40]
The
filter
method doesn’t modify the original array. This is true for all of the array methods discussed in this section.
This is the most commonly-used array method, when working with React.
In many ways, map
is quite a lot like forEach
. We give it a callback function, and it iterates over the array, calling the function once for each item in the array.
The big difference, though: map produces a brand new array, full of transformed values.
The forEach
function will always return undefined
.
const numbers = [1, 2, 3];
const result = numbers.forEach((num) => num + 1);
console.log(result); // undefined
By contrast, map
will collect all the values we return from our callback, and put them into a new array:
Ex-1,
const numbers = [1, 2, 3];
const result = numbers.map((num) => num + 1);
console.log(result); // [2, 3, 4]
Like filter
, map
doesn’t mutate the original array; it produces a brand-new array.
Ex-2
const people = [
{ name: "Aisha", grade: 89 },
{ name: "Bruno", grade: 55 },
{ name: "Carlos", grade: 68 },
{ name: "Dacian", grade: 71 },
{ name: "Esther", grade: 40 },
];
const screamedNames = people.map((person) => {
return person.name.toUpperCase();
});
console.log(screamedNames);
/*
['AISHA', 'BRUNO', 'CARLOS', 'DACIAN', 'ESTHER']
*/
Here’s a helpful way to think about map: it’s exactly like forEach
, except it saves whatever we return from the callback into a new array.
Accessing the index:
Like the other methods we’ve seen, we can pass a second parameter to access the current item’s index:
Ex-3,
const people = [
{ name: "Aisha", grade: 89 },
{ name: "Bruno", grade: 55 },
{ name: "Carlos", grade: 68 },
{ name: "Dacian", grade: 71 },
{ name: "Esther", grade: 40 },
];
const numberedNames = people.map((person, index) => {
return `${index}-${person.name}`;
});
console.log(numberedNames);
/*
['0-Aisha', '1-Bruno', '2-Carlos', '3-Dacian', '4-Esther']
*/
The find
method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
const array1 = [5, 12, 8, 130, 44];
const found = array1.find((element) => element > 10);
console.log(found); // 12
The includes
method determines whether an array includes a certain value among its entries, returning true or false as appropriate.
const pets = ["cat", "dog", "bat"];
console.log(pets.includes("cat")); // true
console.log(pets.includes("at")); // false
The join
method creates and returns a new string by concatenating all of the elements in an array, separated by commas or a specified separator string.
const elements = ["Fire", "Air", "Water"];
console.log(elements.join()); // "Fire,Air,Water"
console.log(elements.join("")); // "FireAirWater"
console.log(elements.join("-")); // "Fire-Air-Water"
The full list of methods available on array are nicely documented in MDN site - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
Using JavaScript, interact with DOM and we can
window
, representing the window in which the script is running, is exposed to JavaScript code.Properties on window
window.document; // represents current document in the window
window.document.head; // represents head section of document in the window
window.innerHeight;
innerWidth;
window
as everything about the currently opened window like browser bar, the tabs, scroll bar. It represents whole browser window, hence the name window
.
document
is only just about current tab’s DOM representation.
document.querySelector("div");
document.body;
document.head;
This stores the info about browser meta data and device that it is on. Things such as web cam and audio access, battery level, GPS coordinates.
window.navigator;
All the interesting things can be done from browser is documented here https://whatwebcando.today/
Let’s say we want to grab a reference to an HTML element that already exists on the page.
We can do this with the querySelector
method:
Ex-1,
const body = document.querySelector('body'); // Grab the <body> tag, will give you the first matching one
const p = document.querySelector("p"); // Grab the <p> tag, will give you the first matching one
console.log(body);
console.log(p);
querySelector
uses CSS selectors like .some-class
and #some-id
.querySelector
can’t find a matching element, it’ll return null
.Sometimes, multiple elements on the page will match the provided query. querySelector
will grab the very first one it finds.
There’s another method, querySelectorAll
, which will collect an array-like object of all the matched elements.
Ex-2,
const lists = document.querySelectorAll("div"); // will give you multiple elements
console.log(lists);
Once we’ve captured a reference to a DOM node, we can do a bunch of stuff with it. Let’s examine some of the operations at our disposal.
Ex-1,
Let’s say, we want to change the text within of a header h2
index.html
<h2>Hi</h2>
<script>
const h2Node = document.querySelector("h2");
console.log(h2Node.textContent);
h2Node.textContent = "Hello";
</script>
textContent
is a writeable property, not a method. Instead of calling it as a function, we re-assign its value to the text content we want to include.
Ex-2,
Here, we are grabbing the h2
element and assigning the CSS class to its className
attribute.
<h2>Hi</h2>
<style>
.active {
color: red;
}
</style>
<script>
const h2ele = document.querySelector("h2");
h2ele.className = "active";
</script>
h2ele.setAttribute('class',"active")
, elementssetAttribute
method also can be used to set any attribute value.- Use
classList
to work multiple classes
let’s talk about creating brand-new DOM nodes from scratch now.
We can do that using the document.createElement
:
const element = document.createElement('div');
The createElement
takes 1 parameter, which serves as the tag to be created. We can pass any valid HTML tag (eg. a
, ul
, footer
).
A newly-created element has no attributes and no content. We can enhance it using the methods and tools we’ve seen so far.
Ex-1:
index.html
const element = document.createElement('div');
element.setAttribute('style', 'color: red;');
element.innerText = "Hello world!";
We’ve created an element, but we don’t see it anywhere on the page!???. The reason for this is that while we’ve created a DOM node, we haven’t attached it anywhere.
We can fix this with the appendChild
method:
index.html
const element = document.createElement('div');
element.setAttribute('style', 'color: red;');
element.innerText = "Hello world!";
const body = document.querySelector('body');
body.appendChild(element);
In order for a DOM node to be visible to the user, it needs to be within the <body>
tag. The user won’t see any HTML tags in other parts of the page. And so when we create an element, it’s associated with the document, but it won’t be visible until we append it somewhere within the <body>
.
Ex-2,
index.html
<body></body>
<script>
// Creating an element and adding attributes to it
const divElement = document.createElement("div");
divElement.textContent = "I am a div";
divElement.classList.add("active");
console.log(divElement);
const imageElement = document.createElement("img");
imageElement.src = "https://picsum.photos/200/300";
imageElement.alt = "A photo";
console.log(imageElement);
// adding or attaching the nodes or elements to DOM
document.body.appendChild(divElement);
divElement.appendChild(imageE);
</script>
Check the browser’s Element tab,
<d class="active">
I am a div <img src="https://picsum.photos/200/300" alt="A photo" />
</d>
Most of the methods we’ve seen, like
querySelector
andappendChild
, can be called on any DOM node.createElement
is different: it can only be called on thedocument
object.
<button>Try Me!</button>
<script>
const but = document.querySelector("button");
function clickHandler() {
console.log("I am being clicked!!!");
}
but.addEventListener("click", clickHandler); // binding
setTimeout(() => {
but.removeEventListener("click", clickHandler); // unbinding
}, 5000);
// In above code we not invoking the fns,
// passing function reference to invoke that fn later.
</script>
A callback function is just a regular function, it’s just a word we use to describe when we pass a function to a method that will then be called at a later point in time.
Equivalent code,
<button onclick="clickHandler()">Try Me!</button>
<script>
function clickHandler() {
console.log("I am being clicked!!!");
}
</script>
<button>Try Me!</button>
<script>
const but = document.querySelector("button");
function clickHandler(e) {
console.dir(e);
console.log(this.textContent); // this means here currently clicked element
}
but.addEventListener("click", clickHandler);
</script>
How to stop the default behavior of an element?
<form>
<input type="submit" value="Submit me!!" />
</form>
<script>
const form = document.querySelector("form");
function clickHandler(e) {
// e.preventDefault();
console.log("Submitted");
}
form.addEventListener("submit", clickHandler);
</script>
Note: uncomment the line and find the difference, when clicking on button
Babel is a JavaScript compiler/transpiler.
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments
// Babel Input: ES2015 arrow function
[1, 2, 3].map((n) => n + 1);
// Babel Output: ES5 equivalent
[1, 2, 3].map(function (n) {
return n + 1;
});
Usage,
CLI - https://babeljs.io/setup#installation
CDN - In-browser transpiling
<div id="output"></div>
<!-- Load Babel -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Your custom script here -->
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById("output").innerHTML = getMessage();
</script>
Online REPL is available for Babel at https://babeljs.io/repl/
for any experimentation.
Installation guide -
https://babeljs.io/setup#installation
fetch()
method that provides an easy, logical way to fetch resources asynchronously across the network.XMLHttpRequest
(AJAX).Syntax,
let promise = fetch(url, [options]);
Ex-1,
const getData = async () => {
let response = await fetch(`https://jsonplaceholder.typicode.com/todos/1`);
if (response.ok) {
return await response.json();
} else {
alert("HTTP-Error: " + response.status);
}
};
const data = await getData();
console.log(data);
Getting a response is usually a two-step process,
The promise, returned by fetch
, resolves with an object of the built-in Response class as soon as the server responds with headers.
We can see HTTP-status in response properties:
status
– HTTP status code, e.g. 200.ok
– boolean, true if the HTTP status code is 200-299.To get the response body, we need to use an additional method call.
response.text()
– read the response and return as text,response.json()
– parse the response as JSON,Ex-2,
Same code with promise pattern,
fetch(`https://jsonplaceholder.typicode.com/todos/1`)
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error("HTTP-Error: " + response.status);
}
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
// get one header
console.log(response.headers.get("Content-Type")); // application/json; charset=utf-8
// iterate over all headers
for (let [key, value] of response.headers) {
console.log(`${key} = ${value}`);
}
// cache-control = max-age=43200
// content-type = application/json; charset=utf-8
// expires = -1
// pragma = no-cache
To set a request header in fetch, we can use the headers
option.
const createPost = async (post) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
method: "POST",
headers: {
"Content-type": "application/json; charset=UTF-8",
},
body: JSON.stringify(post),
});
return await response.json();
};
const postToBeCreated = {
userId: 1,
title: "React",
body: "Library to create user interfaces",
};
const post = await createPost(postToBeCreated);
console.log(post);
React is a JavaScript library for building user interfaces.
Why React?
The idea of taking individual functions and composing them together to get some value is called functional composition in general.
Ex-1,
function getProfilePic(username) {
return "https://github.com/ry/" + username;
}
function getProfileLink(username) {
return "https://github.com/ry" + username;
}
function getFollowCount(username) {
return "https://github.com/ry" + username;
}
function getGitHubProfile(username) {
return {
pic: getProfilePic(username),
link: getProfileLink(username),
follow: getFollowCount(username),
};
}
getGitHubProfile("ry");
``
Ex-2
Shell commands also has this composable principle,
```console
> ls | grep *.js
React takes same idea, instead of composing functions together to get some value, we compose functions together to get some UI.
Ex-3,
function GetProfilePic(props) {
return <image src={"https://github.com/ry/" + props.username} />;
}
function GetProfileLink(props) {
return <image src={"https://github.com/ry/" + props.username} />;
}
function GetFollowCount(props) {
// Get this count from db or services
const count = 10;
return <p>{count}</p>;
}
function GetGitHubProfile(username) {
return (
<div>
<GetProfilePic username={username} />
<GetProfileLink username={username} />
<GetFollowCount username={username} />
</div>
);
}
<GetGitHubProfile username="ry" />;
Composition is a language-agnostic programming principle. The same intuition we have about building and composing functions together can be directly applied to building and composing React components together.
In jQuery or plain JavaScript DOM,
We have event handlers which are responsible for updating the state of the application which primarily lives in the DOM.
In React,
Instead of the source of truth for the state of your application living in the DOM, it lives inside of your React components. From there, we can explicitly decide how and when the state should change as well as what the UI looks like based off of that state.
The way to think about this is that your UI is just a function of your state,
UI = fn(state)
Imperative code,
Here we are giving step by step instruction how logic flow should execute and how to update the DOM.
$("span")
.css("opacity", 1)
.text("myName = " + myName)
.fadeIn(30)
.fadeOut(1000);
Declarative code,
<span style= fadeIn={30} fadeOut={1000}>
{myName}
</span>
Here we don’t give step by step instruction, instead with React, however, that responsibility is abstracted from us. Instead, React allows ud to describe what the UI should look like, not how it gets updated.
In other words, when a component’s state changes, React will do the hard work of actually updating the DOM.
HTML, SQL are example of Declarative programming
Many (if not all) declarative approaches have some sort of underlying imperative abstraction.
In Angular,
<p>Heroes:</p>
<ul>
<li *ngFor="let hero of heroes"></li>
</ul>
In React,
<p>Heroes:</p>
<ul>
{heroes.map((hero) => (
<li>{hero}</li>
))}
</ul>
How to create an h1 element?
index.html
<div id="app"></div>
<script>
const mountNode = document.getElementById("app");
const element = document.createElement("h1");
element.textContent = "Hello World!!";
element.id = "title"; // or element.setAttribute('id', 'title');
mountNode.appendChild(element);
</script>
index.html
<div id="app"></div>
<!-- Obtained from https://reactjs.org/docs/cdn-links.html -->
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script>
const mountNode = document.getElementById("app");
const element = React.createElement("h1", { id: "title" }, "Hello World!!");
const root = ReactDOM.createRoot(mountNode);
root.render(element);
</script>
React
andReactDOM
packages are added towindow
object.
What do we see in browser and Element tab,
<html>
<head></head>
<body>
<div id="app">
<h1 id="title">Hello World!!</h1>
</div>
<!-- Obtained from https://reactjs.org/docs/cdn-links.html -->
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script>
const mountNode = document.getElementById("app");
const element = React.createElement(
"h1",
{ id: "title" },
"Hello World!!"
);
ReactDOM.createRoot(mountNode).render(element);
</script>
</body>
</html>
Let’s break this down,
div#app
, this is where react application is going to be rendered (created), usually called mount-node or container-node. We can have N number of mount-nodes and each one should have corresponding ReactDOM.createRoot().render()
associated with them. so all of them can co-exist, also
this makes easy in embedding the react application in other web projects.react
package holds the API for elements, components, state, props etc.react-dom
package (react’s browser DOM renderer) is bride between React elements tree and the browser DOM. Often, we only use it for mounting react application to the html page with ReactDOM.createRoot().render()`. Likewise we have react-native as renderer for iOS & Android platform.https://github.com/chentsulin/awesome-react-renderer
.React.createElement()
API is responsible for creating react elements like h1, div, input etc.ReactDOM.createRoot(DOMWhere).render(reactWhat)
API is responsible for rendering the react elements to browser DOM (converting react elements or components to respective HTML DOM elements).Let’s break postmortem the createElement
API,
React.createElement(type, props, [...children]);
div
or span
), a React component type (a class
or a function
).children can be multiple elements or nested elements or combo as shown below,
const element = React.createElement(
"div",
{ id: "title" },
React.createElement(
"p",
null,
React.createElement("span", null, "i am nested element")
),
"multiple/nested elements is here"
);
maps to,
<div id="app">
<div id="title">
<p><span>i am nested element</span></p>
multiple/nested elements is here
</div>
</div>
<div id="app"></div>
<!-- Obtained from https://reactjs.org/docs/cdn-links.html -->
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script>
const mountNode = document.getElementById("app");
const myForm = React.createElement(
"div",
null,
React.createElement(
"div",
null,
React.createElement("label", null, "Email "),
React.createElement("input", {
type: "email",
placeholder: "enter email here..",
})
),
React.createElement(
"div",
null,
React.createElement("label", null, "Password "),
React.createElement("input", {
type: "password",
placeholder: "enter password here..",
})
),
React.createElement(
"div",
null,
React.createElement(
"button",
{
onClick: function handleClick() {
alert("yo....");
},
},
"Submit"
)
)
);
ReactDOM.createRoot(mountNode).render(myForm);
</script>
No, we don’t do this…..🤯🤯!! We cannot use these kind of React.createElement API for creating complex html document as it’s bit unmanageable, and unfriendly for web app designers/developer. So, react has a technology called JSX to solve this problem.
For an example, creating a h1 element using React.createElement() API is,
React.createElement("h1", { id: "title" }, "Hello World!!");
Same can be written in JSX as below.
<h1 id="title">Hello World!!</h1>
The latter syntax just looks like HTML. but we need to place the JSX inside the script tag or .js file, it will be interpreted as JavaScript code by browser, but browser doesn’t understand the JSX syntax, so fails to execute.
So, need to transform (transpile) JSX syntax to pure JavaScript code that browser can understand. i.e. JSX syntax to corresponding React.createElement() API function calls.
The transpilation is a process of taking source code and re-writing it to accomplish same results but using the syntax that’s understood by old browsers.
For the sake of exploring JSX syntax, let us use in-browser transformation instead of focusing on build tools.
In-browser (client-side) transpilation is not suitable for production grade apps, it’s only for prototyping or demo. For real-life application we should have build process in place.
Online REPL is available for Babel -https://babeljs.io/repl/
for any experimentation.
<div id="app"></div>
<!-- Obtained from https://reactjs.org/docs/cdn-links.html -->
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<!-- Obtained from https://babeljs.io/en/setup/#installation -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const mountNode = document.getElementById("app");
const element = <h1 id="title"> Hello World!!</h1>;
ReactDOM.createRoot(mountNode).render(element);
</script>
https://unpkg.com/@babel/standalone/babel.min.js
is the In-browser Babel transpiler.React.createElement()
, instead use JSX syntax (looks HTML) to create react elements or components. Babel will take care of transpiling to React.createElement()
API.const element = (
<div id="app">
<div id="title">
<p>
<span>i am nested element</span>
</p>
multiple/nested elements is here
</div>
</div>
);
When building a UI, we often need to use conditional rendering, loops, variables etc . In JSX, it’s allowed to use JavaScript expressions within curly braces { }
(it’s called Interpolation also).
An expression produces a value, example, each of these produces or returns a single value. But statements do not produce values.
1+2
labelValue (means a variable or an identifier)
aFunctionCall()
aFunctionName (means, fn expression)
Think of these, anything that can be on the right side of an assignment is an expression, for an example,
var a = 1 + 2;
var b = labelValue;
var c = aFunctionCall();
var d = aFunctionName;
Statements, for an example,
var a = i = 5; // NOT A VALID CODE
var b = if(true) { 10;}; // NOT A VALID CODE
var c = while(i < 10) { i++ } // NOT A VALID CODE
var d = switch {} // NOT A VALID CODE
No JavaScript statements are allowed such as assigning a value to variable, if-else, switch in JSX.
MDN about expressions -https://developer.mozilla.org/en-US/docs/Web/JavaScript/GuideExpressions_and_Operators#expressions
const time = new Date().getHours();
const greetings = ["Hi", "Hello", "Good morning", "Good noon", "etc"];
const name = "Jeez";
const element = (
<div>
<h3>Greetings,</h3>
<ul>
{greetings.map(function (greet) {
return <li>{greet}</li>;
})}
</ul>
{time < 12 ? <b>Good morning!</b> : <b>I don't care!</b>} {name}
</div>
);
// Greetings,
// Hi
// Hello
// Good morning
// Good noon
// etc
// Good morning! Jeez
Observe, how we inserted our JavaScript expression - ternary operation inside JSX using curly braces { } & Array map for iteration & usage of variables.
Q. How to use JavaScript statements to achieve conditional rendering instead of using ternary, let’s say using if else pattern?
A. Write a function, use any JavaScript syntax and conditionally return a JSX, like below
const mountNode = document.getElementById("app");
const time = new Date().getHours();
function greeting(timeArg) {
if (timeArg < 12) return <b>Good morning!</b>;
else if (timeArg > 12) return <b>Good afternoon!</b>;
else if (timeArg == 12) return <b>I don't care!</b>;
}
const element = <div>{greeting(time)}</div>;
ReactDOM.createRoot(mountNode).render(element);
// Good morning!
const element = (
<div>
{
//<span>1</span> single line comment
}
{/* Multi line comment
<span>2</span>
<span>2</span>
<span>2</span>
*/}
<span>Hmmm....what!!</span>
</div>
);
// Hmmm....what!!
const element = (
<div>
<p>1. More info »</p> {/*HTML ENTITY*/}
<p>2. More info »</p> {/*HTML CODE*/}
<p>3. More info »</p> {/*HEX CODE*/}
<p>4. {"More info »"}</p> {/*HTML ENTITY*/}
<p>5. {"More info \u00BB"}</p> {/*HTML code*/}
</div>
);
// 1. More info »
// 2. More info »
// 3. More info »
// 4. More info »
// 5. More info »
4th one did not work, we had put HTML entity inside the JavaScript expression, it is treated as string expression, so, it’s double encoded or string escaped.
So, that’s why use Unicode version of HTML ENTITY i.e. \u00BB. See in 5th one.
To present risk of XSS (Cross Site Scripting) exploits, JSX forces automatic escaping in expressions. String variables in JSX are escaped automatically.
More information about HTML ENTITIES and their various formats
https://developer.mozilla.org/en-US/docs/Glossary/Entity
https://www.toptal.com/designers/htmlarrows/arrows/
https://dev.w3.org/html5/html-author/charref
Below code WILL NOT work, in console it throws below error, Parse Error: Adjacent JSX elements must be wrapped in an enclosing tag.
const element = (
<p>1st element</p>
<p>2nd element</p>
);
It is React’s limitation. As JSX syntax is converted to JavaScript function call or objects, a function cannot return a multiple objects unless with the help of wrapper object or an array. Also, React expects tree like structure for rendering/re-rendering/re-conciliation, so, it should return single tree root node only.
Solutions, it can be solved in multiple ways as below,
const element1 = (
<React.Fragment>
<p>1st element</p>
<p>2nd element</p>
</React.Fragment>
);
// OR
const element2 = (
<>
<p>1st element</p>
<p>2nd element</p>
</>
);
// OR
// Think of situation where we are forced to return,
// <td>1</td>
// <td>2</td>, then, <div> can't be used as a wrapper.
// So, it's advisable to use solution 1 or 2.
const element3 = (
<div>
<p>1st element</p>
<p>2nd element</p>
</div>
);
// OR
const element4 = [<p key="1">1st element</p>, <p key="2">2nd element</p>];
It’s recommended to use solutions 1 or 2 (2nd one is just a shorthand of 1st one). 3rd one adds unnecessary div & 4th one not is intuitive.
JSX must always return a single root element.
JSX may look like HTML with the advantage of adding dynamic values, loops & conditions. But there are few differences in attributes/props as described below.
class attribute will be className
, Since class
is reserved keyword in JavaScript
const element = <h1 className="title-card">Hello World!!</h1>;
for attribute is htmlFor
in JSX, Since for
is reserved keyword in JavaScript
const element = <label htmlFor="inputid" />;
Is an object in JSX
const element = (
<h1
style=
>
Hello World!!
</h1>
);
It’s not background-color
, we should use camelCase same as JavaScript API - backgroundColor
Are not allowed, even though they are fine in HTML.
const element = <br > /* NOT ALLOWED */
const element1 = <br /> /* ALLOWED */
All the attributes in JSX needs to be camelCase.
function alertMe() {
alert("Yo...");
}
const element = (
<input
onClick={alertMe}
value="Try me"
type="button"
/>
);
In HTML onclick
, but in JSX it is onClick
Exceptions to this rule are all
data-
andaria-
prefixed attributes;these are just like in HTML.
In HTML/JavaScript,
Changing the form control <input>
, content will not update the control property value. Below is the example,
index.html
<label>Greet: </label>
<input id="greet" value="Hello" type="text" onchange="changeHandler()" />
<script>
window.onload = function () {
var control = document.getElementById("greet");
console.log("prop - value : ", control.value);
console.log("prop - defaultValue : ", control.defaultValue);
console.log("attribute - value : ", control.getAttribute("value"));
};
function changeHandler() {
var control = document.getElementById("greet");
console.log("prop - value : ", control.value);
console.log("prop - defaultValue : ", control.defaultValue);
console.log("attribute - value : ", control.getAttribute("value"));
}
</script>
<!--
Page loads,
prop - value : Hello
prop - defaultValue : Hello
attribute - value : Hello
-->
<!--
When we chang the textbox text,
prop - value : Hi
prop - defaultValue : Hello
attribute - value : Hello
-->
value
has a equivalent DOM property called - defaultValue
.value
reflect in defaultValue
and vice versa.value
will be copied to DOM property - value
.value
will not be reflect in either HTML attribute - value
or DOM property - defaultValue
.value
has been updated.In React,
We use value
property for update-to-date content of a control & to specify default or initial data we use defaultValue
prop.
function changeHandler(e) {
console.log("defaultValue prop : ", e.target.defaultValue);
console.log("value prop : ", e.target.value);
}
const element = (
<div>
<label>Greet: </label>
<input
id="greet"
defaultValue="Hello"
type="text"
onChange={changeHandler}
/>
</div>
);
// After the we start changing the text box.
// defaultValue prop : Hello
// value prop : Hellos
// defaultValue prop : Hello
// value prop : Helloss
Instead of defaultValue
, we can use value
prop also, then, value
prop will be copied to defaultValue internally, and we need to have mechanism to update value
prop back.
For example,
Try below change and see why it behaves like that?
Change defaultValue="Hello"
to value="Hello"
in above code.
Result: I’m trying to change the textbox data, the updated data is appearing in the console but not in the control. WHY??? Since, “Hello” text is hard-coded to control, react thinks, every render that is the latest value supplied. Hence re-rendering with the same data.
Solution : React Controlled Component
In HTML,
When using form elements, users change their values when interacting with them.
In React,
We can subscribe to such changes with onChange
attribute. This is more consistent than using checked
for checkboxes & radio buttons, selected
for <select>
etc.
onChange
attributes works in similar fashion for all the form controls such as text, textarea, checkbox, radio, button in React.
function chooseLearn(evt) {
console.log("selected value : ", evt.target.value);
}
const element = (
<div>
Last name <input type="text" onChange={chooseLearn} />
</div>
);
// When we enter text avi in text box
// selected value : a
// selected value : av
// selected value : avi
function chooseLearn(evt) {
console.log("selected value : ", evt.target.value);
}
const element = (
<>
Summary-1 <textarea defaultValue="can be \n set" onChange={chooseLearn} />
<br />
Summary-2 <textarea defaultValue={"can be \n set"} onChange={chooseLearn} />
<br />
Summary-3 <textarea onChange={chooseLearn}>can be \n set </textarea>
<br />
Summary-4 <textarea onChange={chooseLearn}>{"can be \n set"}</textarea>
<br />
</>
);
// Renders in UI
// Summary-1 => can be \n set
// Summary-2 => can be
// set
// Summary-3 => can be \n set
// Summary-4 => can be
// set
Observe,
\n
for multi-line and it works with only JavaScript expression string.<textarea>
through value
is not supported, but in React it is possible just like <text>
using value
or defaultValue
props.value
or defaultValue
instead of using the children node.function chooseLearn(evt) {
console.log("selected value : ", evt.target.value);
console.log("control name : ", evt.target.name);
}
const element = (
<div>
Yes
<input type="radio" value="yes" name="wannalearn" onChange={chooseLearn} />
<br />
No
<input type="radio" value="noo" name="wannalearn" onChange={chooseLearn} />
</div>
);
// Select Yes, then, in console,
// selected value : yes
// control name : wannalearn
// Select No, then, in console,
// selected value : noo
// control name : wannalearn
function chooseLearn(evt) {
console.log("selected value : ", evt.target.value);
}
const element = (
<div>
<h3>How many days in a leap year ??</h3>
<input type="checkbox" value="366" onChange={chooseLearn} />
366
<input type="checkbox" value="365" onChange={chooseLearn} />
365
</div>
);
// Select 366, then, in console,
// selected value : 366
// Select 365, then, in console,
// selected value : 365
<select>
In HTML, to pre-select an option, we use selected
attribute as shown below,
<select>
<option value="yes">Yes</option>
<option value="no" selected>No</option>
</select>
In React, use defaultValue
or value
attribute.
function choosePet(evt) {
console.log("selected value : ", evt.target.value);
}
const element = (
<div>
<h4>Choose a dino pet:</h4>
<select defaultValue="veloci" onChange={choosePet}>
<option value="t-rex">Tyrannosaurus Rex</option>
<option value="tricer">Triceratops</option>
<option value="veloci">Velociraptor</option>
</select>
</div>
);
// When page renders, it selected 'Velociraptor' as default selection
// When Tyrannosaurus Rex selected, in console, we see
// selected value : t-rex
// When Triceratops selected, in console, we see
// selected value : tricer
For multiple selection, pass an array of values & multiple={true}
attribute.
const element = (
<div>
<h4>Choose a dino pet:</h4>
<select
defaultValue={["veloci", "t-rex"]}
multiple={true}
onChange={choosePet}
>
<option value="t-rex">Tyrannosaurus Rex</option>
<option value="tricer">Triceratops</option>
<option value="veloci">Velociraptor</option>
</select>
</div>
);
event or e or evt argument is React’s SyntheticEvent object which is a wrapper around the native JavaScript event object.
The event handler function will receive the event object, which looks a lot like a native browser event. It has the standard stopPropagation
and preventDefault
functions if you need to prevent bubbling or cancel a form submission.
For example. It’s not actually a native event object though – it is a SyntheticEvent .
The event object passed to a handler function is only valid right at that moment. The SyntheticEvent object is pooled for performance. Instead of creating a new one for every event, React replaces the contents of the one single instance in the event pool.
We can’t access the event object asynchronously (say, after a timeout, or after a state update). If you need to access an event asynchronously, call event.persist()
and React will keep it around for us.
evt.target
, will get all the native props such as type, name, value etc.example here with plain react form and Formik etc
Until now, we have learned how to create & use React elements.
React elements are the building blocks of React applications. For an example,
const element = <h1>Hello, world</h1>;
React components are small, reusable pieces of code that return a React element to be rendered to the page.
The simplest version of React component is a plain JavaScript function that returns a React element:
function Welcome() {
return <h1>Hello, World</h1>;
}
Typically, elements are not used directly, but get returned from components.
But, how to render React components??
We can use same React.createElement()
API to create an “instance” or object of component or JSX way.
Using React.createElement()
const mountNode = document.getElementById("app");
function Welcome() {
return <h1>Hello, World</h1>;
}
const anInstance = React.createElement(Welcome);
ReactDOM.createRoot(mountNode).render(anInstance);
// Can be used directly inside the render method
// ReactDOM.createRoot(mountNode).render(React.createElement(Welcome));
JSX way
const mountNode = document.getElementById("app");
function Welcome() {
return <h1>Hello, World</h1>;
}
const anInstanceJsx = <Welcome />;
ReactDOM.createRoot(mountNode).render(anInstanceJsx);
// Can be used directly inside the render method
// ReactDOM.createRoot(mountNode).render(<Welcome />);
- Always start component names with a capital letter.
- React treats components starting with lowercase letters as DOM tags (React elements).
- For example,
<div />
represents an HTML div tag, but<Welcome />
represents a component.
Until now, we have been using below techniques for our discussion as a development environment,
When we build a prod application, we cannot use the above techniques or tools as they are not meant for prod use.
In real apps, we need transpiler, minify JS & CSS, bundling, bundle splitting etc all at the build time.
Install Node.js and npm - https://nodejs.org/en/
> node --version
v14.16.1
> npm --version
6.14.12
Let’s build our own build tool using webpack.
App.js ---> | | |
Contact.js ---> | Transform | Bundler | -> bundle.js
Sales.js ---> | (optional) | |
Official docs
https://webpack.js.org/
https://webpack.js.org/concepts/
Problem with existing workflow?
<body>
// some HTML
<script src="https://unpkg.com/three@0.136.0/build/three.js"></script>
<script src="libs/lodash.min.js"></script>
<script src="src/Contact.js"></script>
<script src="src/Sales.js"></script>
</body>
Problems with above approach is typos, order of scripts, cross browser compatibility of code inside each script etc.
Solution,
<body>
<script src="dist/bundle.js"></script>
</body>
> mkdir react-arena; cd react-arena; echo "" > webpack.config.js; echo "" > index.html; npm init -y; mkdir src; echo "" > src/index.js; echo "" > src/styles.css
Directory structure
react-arena
├── webpack.config.js
├── src
│ └── index.js
├── package.json
├── index.html
npm install --save-dev webpack webpack-cli
The whole point of webpack is to “examine all of your modules, (optionally) transform them, then intelligently put all of them together into one or more bundle(s)” , in order to do that, it needs to know three things.
A typical JavaScript application is composed of modules, for all of them, there must be root ES module file. Let’s say src/index.js
// webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
};
Now, run webpack, let it bundle our code and generate bundle.js
C:\\...\react-mern > npx webpack
react-arena
├── dist
│ └── bundle.js
When webpack
is building its dependency graph by examining all of your import / require()
statements, it’s only able to process JavaScript and JSON files. It DOES NOT KNOW how to process .css
, .svg
, .jpg
etc.
// webpack can read and process these files (modules)
import auth from "./api/auth.js"; //
import config from "./utils/config.json"; //
// webpack DOES NOT KNOW how to process below files
import "./styles.css"; // ⁉
import logo from "./assets/logo.svg"; // ⁉
import "./styles.scss"; // ⁉
loader
,as the name suggests, is to give webpack the ability to process more than just JavaScript and JSON files. It teaches webpack how to process other kind of assets and convert them into valid modules that can be consumed by your application and added to the dependency graph.These loaders are separate node modules/packages, we should install them and add it to webpack.config.js
module.rules
.test
property identifies which file or files should be transformed.use
property indicates which loader should be used to do the transforming.Ex,
svg-inline-loader
this webpack loader inlines SVG file as module.
npm install --save-dev svg-inline-loader
// webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [{ test: /\.svg$/, use: "svg-inline-loader" }],
},
};
css-loader
we will be able to import .css files.style-loader
instructs webpack to inject imported CSS in to DOM through a <style>
tag.babel-loader
loader for transforming “next generation JavaScript” to the JavaScript of today that browsers can understand using Babel (that is - @babel/core
, @babel preset-env
& @babel/preset-react
@babel/core
core Babel library@babel/preset-env
Babel preset that lets you specify an environment (browsers or ECMA script version) and automatically enables the necessary plugins. It determines which features needs to be transformed to run within different browsers or runtime versions.@babel/preset-react
Babel preset is used to transform all your React JSX into functions call.So,
npm install --save-dev style-loader css-loader babel-loader @babel/preset-env @babel/preset-react @babel/core
// webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{ test: /\.svg$/, use: "svg-inline-loader" },
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{
test: /\.(js)$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
},
],
},
};
Notice that we have style-loader
before css-loader
. This is important. webpack
will process those in reverse order. So css-loader
will interpret the import './styles.css'
line then style-loader
will inject that CSS into the DOM.
So the full process looks something like this.
./src/index.js
.Plugins allow you to execute certain tasks after the bundle has been created. Because of this, these tasks can be on the bundle itself, or just to our codebase.
HtmlWebpackPlugin
This plugin will generate an HTML5 file for you that includes all your webpack bundles in the body using script
tags.
npm install --save-dev html-webpack-plugin
echo "" > index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>react-arena</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Now, so will tell HtmlWebpackPlugin
to use this as a template and create a HTML page in dist/ folder with script tag referencing bundle.js.
so,
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{ test: /\.svg$/, use: "svg-inline-loader" },
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{
test: /\.(js)$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html", // Where and what is the name of the HTML page to create.
template: "./index.html", // This specifies which file to use as template for the index.html being created.
}),
],
};
Now, run webpack, let it bundle our code and generate bundle.js
C:\\...\react-mern > npx webpack
react-arena
├── dist
│ └── bundle.js
│ └── index.html
dist/index.html - observe the script tag
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>react-arena</title>
<script defer="defer" src="bundle.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
webpack-dev-server is a development server for webpack . Instead of generating a dist directory, it’ll keep track of your files in memory and serve them via a local server. More than that, it supports live reloading.
npm install webpack-dev-server --save-dev
We will use npm-scrips to achieving the mode switching. package.json
package.json
"scripts": {
"build": "SET NODE_ENV='production' && webpack",
"start": "webpack-dev-server"
},
Final webpack.config.js
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.svg$/,
use: "svg-inline-loader",
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{
test: /\.(js)$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html", // Where and what is the name of the HTML page to create.
template: "./index.html", // This specifies which file to use as template for the index.html being created.
}),
],
mode: process.env.NODE_ENV === "production" ? "production" : "development",
};
Till now whatever we did is for any general web development with webpack.
npm install --save react react-dom
create addition filed like below.
react-arena
├── webpack.config.js
├── src
│ └── index.js
│ └── styles.css
├── package.json
├── index.html
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Hello({ name }) {
return (
<div className="title-card ">
<h1>Hello {name}</h1>
</div>
);
}
const mountNode = document.querySelector("#root");
ReactDOM.createRoot(mountNode).render(<Hello name="World!!"></Hello>);
/* src/styles.css */
.title-card {
color: blue;
}
Run app in development mode,
npm start
http://localhost:8080/
Run app in production mode to generate the static build files,
npm run build
This will generate the asset like below,
react-arena
├── dist
│ └── bundle.js
│ └── index.html
Use any server application like VSCode ext or Chrome ext to run this static
dist
folder and open index.html file.
Complete code available here avinash2wards/react-arena with instructions to run.
This CLI app uses webpack as a bundler
npx create-react-app my-app
cd my-app
npm start
That’s all.
Whatever we been writing by ourself its already built-in.
Look at their source code CRA-webpack.config.js
Note: npm run eject
to see the webpack.config.js locally.
Documentation for CRA can be found here - CRA
This CLI app uses esbuild and rollup as a bundler
npm init vite@latest
cd vite-project
npm install
npm run dev
Documentation for Vite can be found here - Vite
Let’s build a meaningful user interface using React components.
A Tweet
The mental model to build above UI is goes like, a Tweet is a parent component which consist of smaller components in given placement. So the hierarchy looks like below,
Create and start the app.
npx create-react-app a-react-tweet
cd a-react-tweet
npm start
Add Font Awesome CDN to index.html
<link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.0.12/css/all.css"
/>
src/index.css
.tweet {
border: 2px solid #ccc;
width: 564px;
min-height: 68px;
padding: 10px;
display: flex;
flex-direction: column;
font-family: "Helvetica", arial, sans-serif;
font-size: 14px;
line-height: 18px;
}
.header {
display: flex;
}
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
function Tweet() {
return (
<div className="tweet">
<div className="header">Tweet</div>
</div>
);
}
ReactDOM.createRoot(document.querySelector("#root")).render(<Tweet />);
A basic layout will be rendered something like this,
src/Avatar.js;
import React from "react";
import "./Avatar.css";
function Avatar() {
return <img src="https://i.pravatar.cc/50" className="avatar" alt="avatar" />;
}
export { Avatar };
src/Avatar.css
.avatar {
width: 50px;
height: 50px;
border-radius: 5px;
margin-right: 10px;
}
src/index.js; updated,
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { Avatar } from "./Avatar";
function Tweet() {
return (
<div className="tweet">
<div className="header">
<Avatar />
</div>
</div>
);
}
ReactDOM.createRoot(document.querySelector("#root")).render(<Tweet />);
src/NameWithHandle.js
import React from "react";
import "./NameWithHandle.css";
function NameWithHandle() {
return (
<span className="name-with-handle">
<div className="name">Tim</div>
<div className="handle">@Playing_Dad</div>
</span>
);
}
export { NameWithHandle };
src/NameWithHandle.css
.name {
font-weight: bold;
margin-bottom: 0.1em;
}
.handle {
color: #8899a6;
font-size: 13px;
margin-bottom: 0.1em;
}
src/Message.js
import React from "react";
import "./Message.css";
function Message() {
return (
<div className="message">
[At dinner] <br />
Daughter: Daddy, how much of this meatball is meat?
<br />
Me: Probably like 90% <br />
D: So it's 10% balls? <br />
Me:*spits out food*
</div>
);
}
export { Message };
src/Message.css
.message {
color: #584600;
font-size: 15px;
margin-top: 0.5em;
line-height: 25px;
}
src/index.js; updated,
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { Avatar } from "./Avatar";
import { Message } from "./Message";
import { NameWithHandle } from "./NameWithHandle";
function Tweet() {
return (
<div className="tweet">
<div className="header">
<Avatar />
<NameWithHandle />
</div>
<Message />
</div>
);
}
ReactDOM.createRoot(document.querySelector("#root")).render(<Tweet />);
src/ActionButtons.js
import React from "react";
import "./ActionButtons.css";
const Time = () => <span className="time">2:39 AM - 3 Jan 2016</span>;
const ReplyButton = () => <i className="fa fa-reply reply-button" />;
const RetweetButton = () => (
<i className="fa fa-retweet retweet-button">
<span> 986</span>
</i>
);
const LikeButton = () => (
<i className="fa fa-heart like-button">
<span> 1,404</span>
</i>
);
export { Time, ReplyButton, RetweetButton, LikeButton };
src/ActionButtons.css
.time {
color: #8899a6;
}
src/index.js; updated,
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { Avatar } from "./Avatar";
import { Message } from "./Message";
import { NameWithHandle } from "./NameWithHandle";
import { Time, ReplyButton, LikeButton, RetweetButton } from "./ActionButtons";
function Tweet() {
return (
<div className="tweet">
<div className="header">
<Avatar />
<NameWithHandle />
</div>
<Message />
<div className="time-container">
<Time />
</div>
<div className="buttons">
<ReplyButton />
<RetweetButton />
<LikeButton />
</div>
</div>
);
}
ReactDOM.createRoot(document.querySelector("#root")).render(<Tweet />);
src/index.css; updated,
.tweet {
border: 2px solid #ccc;
width: 564px;
min-height: 68px;
padding: 10px;
display: flex;
flex-direction: column;
font-family: "Helvetica", arial, sans-serif;
font-size: 14px;
line-height: 18px;
}
.header {
display: flex;
}
.time-container {
padding-top: 0.3em;
padding-bottom: 0.3em;
font-size: 1.1em;
}
.buttons {
margin-top: 10px;
margin-left: 2px;
font-size: 1.4em;
color: #aab8c2;
}
.buttons span {
font-size: 0.9em;
font-weight: normal;
color: #8899a6;
}
.buttons i {
width: 80px;
}
The full source code available here avinash2wards/react-tweet with instructions to run.
The final component will be like,
Try adding more components like Follow, ShareOptions etc.
Now the Tweet’s content is static/hard-coded, but will learn how to pass the data dynamically through properties (props) & make it reusable component by generating list of Tweets.
attributes
props
(short for “properties”).Props are to components what arguments are to functions.
Props carry the data from parent components to children & vice versa.
We can pass any valid expression just like in JSX as props to any React components.
The JavaScript statements cannot be used!! again!.
import React from "react";
import ReactDOM from "react-dom";
const isOfficial = true;
const showCapital = false;
const getCoordinates = () => "20.5937° N, 78.9629° E";
function CountryCard() {
return <div />;
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(
<CountryCard
className="country-detail"
name={isOfficial ? "Republic of India" : "India"}
capital={showCapital && "Delhi"}
independence={1900 + 47}
population={133.92}
coordinates={getCoordinates()}
languages={["Hindi", "English", "Kannada"]}
/>
);
Props are passed as the first argument to a component function, like this: and it’s an object as you see in the console.
function CountryCard(props) {
console.log(props);
return (
<div>
<p>Name : {props.name}</p>
<p>Capital : {props.capital}</p>
<p>Independence : {props.independence}</p>
<p>Population : {props.population} crores</p>
<p>
Coordinates : <code>{props.coordinates} </code>
</p>
<div>
Languages :
{
<ol>
{props.languages.map((lang) => {
return <li>{lang}</li>;
})}
</ol>
}
</div>
</div>
);
}
// In UI
/*
Name : Republic of India
Capital :
Independence : 1947
Population : 133.92 crores
Coordinates : 20.5937° N, 78.9629° E
Languages :
1. Hindi
2. English
3. Kannada
*/
// In Console
/*
{
"className": "country-detail",
"name": "Republic of India",
"capital": false,
"independence": 1947,
"population": 133.92,
"coordinates": "20.5937° N, 78.9629° E",
"languages": [
"Hindi",
"English",
"Kannada"
]
}
*/
We ca use object destructing for argument and arrow function for component definition
If we cannot change the props, then how to communicate/send data from child to parent component??
If a child needs to send data to its parent, the parent can send down a function as a prop, like this:
Ex-1,
import React from "react";
import ReactDOM from "react-dom";
function handleAction(event) {
console.log("coming from child component:", event);
}
function Parent() {
return <Child onAction={handleAction} />;
}
function Child(props) {
return <button onClick={props.onAction}>Child</button>;
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Parent />);
// In Consol, after clicking on button
// coming from child component: SyntheticBaseEvent {_reactName: 'onClick' …}
Ex-2, A practical one,
goes here
props.children
is what is used to display whatever we include between the opening and closing tags when invoking a component.
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
const MovieTitleCard = (props) => {
console.log(props);
return <h3>I'm from MovieTitleCard & staying in MovieTitleCard</h3>;
};
ReactDOM.createRoot(rootElement).render(
<MovieTitleCard>What!!! Am i abandoned???</MovieTitleCard>
);
// In UI,
// I'm from MovieTitleCard & staying in MovieTitleCard
// In Console
// {
// "children": "What!!! Am i abandoned???"
// }
MovieTitleCard
, it will pass all of its sub-elements (in this case, the text “What!!! Am i abandoned?? ”) into MovieTitleCard as a prop called children
. We can see that in console.Let’s capture children prop & render.
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
const MovieTitleCard = (props) => {
const { children } = props;
return (
<>
<h3>I'm from MovieTitleCard & staying in MovieTitleCard</h3>
<h4>{children}</h4>
</>
);
};
ReactDOM.createRoot(rootElement).render(
<MovieTitleCard>
I'm an alien, I don't know where I am from but now staying in MovieTitleCard
</MovieTitleCard>
);
// In UI,
// I'm from MovieTitleCard & staying in MovieTitleCard
// I'm an alien, I don't know where I am from but now staying in MovieTitleCard
This can be achieved by passing normal props like children={"sometext"}
. prop name need not be children it can be anything.
More practical example,
Think of passing a graphic movie name, like passing nested elements, then its natural to use children prop. For an example,
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
const MovieTitleCard = ({ children, releaseDate }) => {
return (
<>
<p>Releasing on {releaseDate}</p>
{children}
</>
);
};
ReactDOM.createRoot(rootElement).render(
<MovieTitleCard releaseDate={2015}>
<div>
<p>
MAD MAX <sub>FURY ROAD</sub>
</p>
<img src="https://i.gifer.com/JEbf.gif" height={200} alt="fire" />
</div>
</MovieTitleCard>
);
Output UI,
This way we can build reusable components easily.
For example error message boxes, Modal dialog etc where layout is fixed but the content is passed based on the situations.
There is, however, one major limitation to this pattern: props.children must be rendered as-is.
Q. What if we want the host component (MovieTitleCard) to pass its own props or some data to those children?
A/solution: Render Props or Function as Children pattern.
CHECK CLASS BASED COMPONENT SECTION.
children
prop will be.It could be anything. React provides utility functions for dealing with this opaque data structure.
map and forEach: They accept children, whether it’s a single element or an array, and a function that’ll be called for each element. forEach iterates over the children and returns nothing, whereas map returns an array made up of the values you return from the function you provide.
count: it returns the number of items in children.
toArray: it converts children into a flat array, whether it was an array or not.
only: returns the single child, or throws an error if there is more than one child.
We have access to every child element individually, so you can reorder them, remove some, insert new ones, pass the children down to further children, and so on.
For an example,
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
function Nav({ children }) {
const oldMenus = React.Children.toArray(children);
const newMenus = [];
for (let i = 0; i < oldMenus.length; i++) {
newMenus.push(oldMenus[i]);
newMenus.push(" | ");
}
newMenus.push(<NavItem>Help</NavItem>);
return <ul>{newMenus}</ul>;
}
function NavItem({ children }) {
return (
<li style=>
<a href="#">{children}</a>
</li>
);
}
ReactDOM.createRoot(rootElement).render(
<Nav>
<NavItem key="1">Home</NavItem>
<NavItem key="2">Products</NavItem>
<NavItem key="3">Services</NavItem>
</Nav>
);
// In UI,
// Home | Products | Services | Help
import React from "react";
import ReactDOM from "react-dom";
class DisplayMonitorClass extends React.Component {
render() {
return (
<p>
{this.props.message} and size is {this.props.size}
</p>
);
}
}
DisplayMonitorClass.defaultProps = {
size: "21 inch",
};
function DisplayMonitorFunc({ size = "21 inch", message }) {
return (
<p>
{message} and size is {size}
</p>
);
}
const = document.querySelector("#root");
ReactDOM.createRoot(mountNode).render(
<>
<h3>Class component</h3>
<DisplayMonitorClass size="14 inch" message="prop has be en passed" />
<DisplayMonitorClass message="No prop has been passed, so using the default prop" />
<hr />
<h3>Functional component</h3>
<DisplayMonitorFunc size="14 inch" message="prop has been passed" />
<DisplayMonitorFunc message="No prop has been passed, so using the default prop" />
</>
);
// In UI
/*
Class component
prop has be en passed and size is 14 inch
No prop has been passed, so using the default prop and size is 21 inch
Functional component
prop has been passed and size is 14 inch
No prop has been passed, so using the default prop and size is 21 inch
*/
We’ll take the a-tweet-react example and rework it to display dynamic data by using props.
src/Avatar.js; updated,
import React from "react";
import "./Avatar.css";
function Avatar({ gravatar }) {
return <img src={gravatar} className="avatar" alt="avatar" />;
}
export { Avatar };
src/Message.js; updated,
import React from "react";
import "./Message.css";
function Message({ text }) {
return <div className="message" dangerouslySetInnerHTML= />;
}
export { Message };
dangerouslySetInnerHTML
is React’s replacement for using innerHTML
in the browser DOM. Since we have <br/>
in the text, React will escape them by default, so, using dangerouslySetInnerHTML
.
src/NameWithHandle.js; updated,
import React from "react";
import "./NameWithHandle.css";
function NameWithHandle({ author }) {
const { handle, name } = author;
return (
<span className="name-with-handle">
<div className="name">{name}</div>
<div className="handle">{handle}</div>
</span>
);
}
export { NameWithHandle };
src/ActionButtons.js; updated,
import React from "react";
import "./ActionButtons.css";
const Time = ({ time }) => <span className="time">{time}</span>;
const ReplyButton = () => <i className="fa fa-reply reply-button" />;
const RetweetButton = ({ count }) => (
<i className="fa fa-retweet retweet-button">
<span> {count}</span>
</i>
);
const LikeButton = ({ count }) => (
<i className="fa fa-heart like-button">
<span> {count}</span>
</i>
);
export { Time, ReplyButton, RetweetButton, LikeButton };
src/TWEETS_DATA.js; added
const TWEETS_DATA = [
{
id: 1,
message: `life is soup, i am a fork
`,
gravatar: "https://i.pravatar.cc/50",
author: {
handle: "@pakalupapito",
name: "pakalu papito",
},
likes: 420,
retweets: 840,
timestamp: "2:39 AM - 3 Jan 2016",
},
{
id: 2,
message: `
[At dinner] <br />
Daughter: Daddy, how much of this meatball is meat?
<br />
Me: Probably like 90% <br />
D: So it's 10% balls? <br />
Me:*spits out food*
`,
gravatar: "https://i.pravatar.cc/50",
author: {
handle: "@Playing_Dad",
name: "Tim",
},
likes: 1404,
retweets: 986,
timestamp: "2:39 AM - 3 Jan 2016",
},
{
id: 3,
message: `
i pretend i don't care but deep down i really
still don't care
`,
gravatar: "https://i.pravatar.cc/50",
author: {
handle: "@pakalupapito",
name: "pakalu papito",
},
likes: 110,
retweets: 9,
timestamp: "2:39 AM - 3 Jan 2016",
},
];
export { TWEETS_DATA };
src/index.js; updated,
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { Avatar } from "./Avatar";
import { Message } from "./Message";
import { NameWithHandle } from "./NameWithHandle";
import { Time, ReplyButton, LikeButton, RetweetButton } from "./ActionButtons";
import { TWEETS_DATA } from "./TWEETS_DATA";
function Tweets(props) {
const { tweets } = props;
return (
<>
{tweets.map((t) => (
<Tweet key={t.id} tweet={t} />
))}
</>
);
}
function Tweet({ tweet }) {
return (
<div className="tweet">
<div className="header">
<Avatar gravatar={tweet.gravatar} />
<NameWithHandle author={tweet.author} />
</div>
<Message text={tweet.message} />
<div className="time-container">
<Time time={tweet.timestamp} />
</div>
<div className="buttons">
<ReplyButton />
<RetweetButton count={tweet.retweets} />
<LikeButton count={tweet.likes} />
</div>
</div>
);
}
ReactDOM.createRoot(document.querySelector("#root")).render(
<Tweets tweets={TWEETS_DATA} />
);
The final output,
The full source code available here avinash2wards/react-tweet-with-props with instructions to run.
Challenge:
Build - Credit/Debit, PAN, Aadhar card components or a Facebook post.
Till this point we’ve used props to pass data to components those were pretty static or stateless.
What to Put in State?
As a general rule, data that is stored in state should be referenced inside render somewhere.
Component state is for storing UI state – things that affect the visual rendering of the page. This makes sense because any time state is updated, the component will re-render.
that is,
UI = fn(State)
So, If modifying a piece of data does not visually change the component, that data shouldn’t go into state.
Here are some things that make sense to put in state:
Ex-1,
Why searchTerm is not updating in UI, though it show latest value in console?
import React from "react";
import ReactDOM from "react-dom";
const Form = () => {
let searchTerm = "lucky";
const handleChange = (e) => {
searchTerm = e.target.value;
console.log(searchTerm);
};
return (
<form>
<input onChange={handleChange} />
<div>You are searching for : {searchTerm}</div>
</form>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Form />);
useState
comes built-in with React and can be accessed via React.useState
It takes in a single argument, the initial value for that piece of state, and returns an array with the first item being the state value and the second item being a way to update that state.
Ex-1
import React from "react";
import ReactDOM from "react-dom";
const Form = () => {
let [searchTerm, setSearchTerm] = React.useState("lucky");
const handleChange = (e) => {
setSearchTerm(e.target.value);
};
return (
<form>
<input onChange={handleChange} />
<div>You are searching for : {searchTerm}</div>
</form>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Form />);
// Now searchTerm is in sync with UI
But, when page first renders, i want default searchTerm to show in the text as well? How do i do it?
Ex-2
Controlled Component
import React from "react";
import ReactDOM from "react-dom";
const Form = () => {
let [searchTerm, setSearchTerm] = React.useState("lucky");
const handleChange = (e) => {
// Filter out, special characters number etc etc
let temp = e.target.value.replace(/[^a-zA-Z]+/, "");
setSearchTerm(temp);
};
return (
<form>
<input value={searchTerm} onChange={handleChange} />
<div>You are searching for : {searchTerm}</div>
</form>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Form />);
// Try typing anything other than text, it will sanitize it.
Note : Remove
onChange={handleChange}
oninput
control and observe the behavior.
useState
allows you to,
Trigger Re-renders
Whenever we invoke the updater function that useState gives us, assuming the argument you pass in is different from the current state value, React will cause a re-render to the component, updating the UI.
Preserve Values
Typically when you invoke a function in JavaScript, unless you’re utilizing closures you expect any values defined in that function to get garbage collected once the function is finished executing and you expect each subsequent call to that function to produce its own unique values. But in React.useState it does preserve.
So, what is useState
now?
useState is a tool to add state to functional components and to preserve values between function calls/renders and to trigger a re-render of the component.
We’ll learn of another Hook (useRef) that, like useState, allows you to preserve values between renders but, unlike useState, won’t trigger a rerender to the component.
Whenever you’re setting the current state based on the previous state, you’ll want to pass a function to your updater function so you get the correct, most up to date value. This’s because setting/updating the state is asynchronous.
Ex-3
import React from "react";
import ReactDOM from "react-dom";
const Counter = () => {
const [count, setCount] = React.useState(0);
const increment = () => setCount((count) => count + 1);
const decrement = () => setCount((count) => count - 1);
return (
<React.Fragment>
<button onClick={decrement}>-</button>
<h1>{count}</h1>
<button onClick={increment}>+</button>
</React.Fragment>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Counter />);
In smaller apps, we may get the most updated value by just putting count+1 , but in big apps where it involved heavy computation;the function updater is must i.e. count => count + 1
Q. What if the initial value for a piece of state was the result of an expensive calculation?
A.
Ex-4
import React from "react";
import ReactDOM from "react-dom";
const getExpensiveCount = () => {
console.log("Calculating initial count");
return 999;
};
const Counter = () => {
const [count, setCount] = React.useState(() => getExpensiveCount()); // Or just getExpensiveCount
const increment = () => setCount((count) => count + 1);
const decrement = () => setCount((count) => count - 1);
return (
<React.Fragment>
<button onClick={decrement}>-</button>
<h1>{count}</h1>
<button onClick={increment}>+</button>
</React.Fragment>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Counter />);
// Look in the console, below message only prints on first render, there onwards
// it will use the state value from react internals and ignore the initial state value.
// Calculating initial count
Change from
() => getExpensiveCount()
togetExpensiveCount()
, now this is same as passing the value directly. Difference is on every re-render of component, unnecessary this function gets invoked and causes memory leakage.
Check the message prints multiple times.
More examples,
Ex-5,Todo app,
import React from "react";
import ReactDOM from "react-dom";
function generateId() {
return new Date().getTime(); // Represents milliseconds elapsed between 1 January 1970 00:00:00 UTC and the given date.
}
const Todo = () => {
const [todos, setTodos] = React.useState([]);
const [input, setInput] = React.useState("");
const handleSubmit = () => {
setTodos((todos) =>
todos.concat({
text: input,
id: generateId(),
})
);
setInput("");
};
const removeTodo = (id) =>
setTodos((todos) => todos.filter((t) => t.id !== id));
return (
<div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="New Todo"
/>
<button onClick={handleSubmit}>Submit</button>
<ul>
{todos.map(({ text, id }) => (
<li key={id}>
<span>{text}</span>
<button onClick={() => removeTodo(id)}>X</button>
</li>
))}
</ul>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Todo />);
Ex,
// 1. Mutating non-local variables
function addTodo(todo) {
todos.push(todo);
}
// 2. making network requests
function getData() {
return fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => console.log(json));
}
// 3. updating the DOM
function updateDocumentTitle(title) {
document.title = title;
document.addEventListener("scroll", () => {});
// May be the #name element is in the dom, want to directly talk to it
document.querySelector("#name").addEventListener("change", () => {});
}
Whenever we want to interact with the world, outside of React (whether that’s to make a network request, manually update the DOM, etc.), we’d reach for useEffect
.
To add a side effect to your React component, you invoke useEffect passing it a function which defines your side effect.
Ex-1,
import React from "react";
import ReactDOM from "react-dom";
const Example = () => {
React.useEffect(() => {
console.log("Hi, useEffect");
document.title = "Hi, useEffect";
});
return <></>;
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Example />);
// In console and chrome's tab name,
// Hi, useEffect
By default, React will re-invoke the effect after every render or re-render.
The console.count()
method logs the number of times that this particular call to count() has been called.
Ex-2,
import React from "react";
import ReactDOM from "react-dom";
const Counter = () => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.count("In useEffect, after render");
document.title = `I'ma also changing - ${count}`;
});
console.count("Rendering");
return (
<>
<button onClick={() => setCount((c) => c - 1)}>-</button>
<h1>Count: {count}</h1>
<button onClick={() => setCount((c) => c + 1)}>+</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Counter />);
When initial render,
Click on [+] button 2 times (re-render), we arrive at below result.
The Mental Model
Initial Render
count: 0
Register Effect: () => document.title = `I'm also changing - 0`
Description of UI (in memory): `Count: 0`
React: Updates the DOM
Browser: Re-paints with DOM updates
React invokes Effect: () => document.title = `I'm also changing - 0`
User clicks on [+] Button
React increments "count" by 1, **causing a re-render**
Next Render
count: 1
Register Effect : () => document.title = `I'm also changing - 1`
Description of UI (in memory): `Count: 1`
React: Updates the DOM
Browser: Re-paints with DOM updates
React invokes Effect: () => document.title = `I'm also changing - 1`
User clicks on [+] Button, again
React increments "count" by 1, **causing a re-render**
Next Render
count: 2
Register Effect : () => document.title = `I'm also changing - 2`
Description of UI (in memory): `Count: 2`
React: Updates the DOM
Browser: Re-paints with DOM updates
React invokes Effect: () => document.title = `I'm also changing - 2`
Ex-3, useEffect with HTTP call as a side effect
import React from "react";
import ReactDOM from "react-dom";
const getGithubProfile = async (username) => {
const res = await fetch(`https://api.github.com/users/${username}`);
return res.json();
};
const Profile = () => {
const [profile, setProfile] = React.useState(null);
React.useEffect(() => {
getGithubProfile("ry").then((data) => {
// Uncommenting the below line will cause an infinite loop;
// setProfile(data);
});
});
if (profile === null) {
return <p>Loading...</p>;
}
return (
<>
<p>{profile.name}</p>
<p>{profile.blog}</p>
<p>{profile.location}</p>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Profile />);
The Mental Model
Our component gets rendered then invokes our effect, which updates our state, which triggers a re-render, which then invokes our effect, which updates our state, which triggers a re-render, and on and on. So, result in an infinite loop.
Initial Render
profile: null
Register Effect: () => getGithubProfile('ry').then(setProfile)
Description of UI (in memory): Loading...
React: Updates the DOM
Browser: Re-paints with DOM updates
React invokes Effect: () => getGithubProfile('ry').then(setProfile)
setProfile was invoked
React updates "profile", **causing a re-render**
Next Render
profile: { name: "Ryan Dahl", id: 80 }
Register Effect: () => getGithubProfile('ry').then(setProfile)
Description of UI (in memory): <p>Ryan Dahl</p> ...
React: Updates the DOM
Browser: Re-paints with DOM updates
React invokes Effect: () => getGithubProfile('ry').then(setProfile)
setProfile was invoked
React updates "profile", **causing a re-render**
Next Render
.
.
.
setProfile was invoked
React updates "profile", **causing a re-render**
Next Render
.
.
.
**So, Causing infinite loop**
How to solve this issue?
useEffect
receives second argument, that must be used to control when to invoke useEffect.
If we don’t pass the second argument, then useEffect initial render and every subsequent re-renders.
React.useEffect(() => {
// Will be invoked on the initial render
// and all subsequent re-renders.
});
If we pass an array of all outside values the effect depends on, then React can infer when to re-invoke the effect based on if any of those values change between renders.
React.useEffect(() => {
// Will be invoked on the initial render
// and when "id" or "name" changes.
}, [id, name]);
If we pass an empty array, then useEffect only be invoked on the initial render.
React.useEffect(() => {
// Will only be invoked on the initial render
}, []);
Q. So, which one to use among these 3 to stop the infinite loop issue we faced in above example ???
A. Since we hard-coded the username to be ry , our effect isn’t dependent on any outside values. This means we can safely pass an empty array as the second argument so our effect will only be invoked on the initial render.
React.useEffect(() => {
getGithubProfile("ry").then((data) => {
setProfile(data);
});
}, []);
Lets rewrite the code fetch the multiple profiles based the username.
By adding username
inside of our effect, we’ve introduced an outside value that it depends on. This means we can no longer use an empty array as our second argument. Update the dependency array with what the effect depends on, username
.
import React from "react";
import ReactDOM from "react-dom";
const getGithubProfile = async (username) => {
const res = await fetch(`https://api.github.com/users/${username}`);
return res.json();
};
const Profile = ({ username }) => {
const [profile, setProfile] = React.useState(null);
React.useEffect(() => {
getGithubProfile(username).then((data) => {
setProfile(data);
});
}, [username]);
if (profile === null) {
return <p>Loading...</p>;
}
return (
<>
<p>{profile.name}</p>
<p>{profile.blog}</p>
<p>{profile.location}</p>
</>
);
};
const ProfileContainer = () => {
const [user, setUser] = React.useState("ry");
return (
<>
<button onClick={(e) => setUser("ry")}>ry</button>
<button onClick={(e) => setUser("tj")}>tj</button>
<Profile username={user} />
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<ProfileContainer />);
Now, anytime username
changes (and only when username changes), once the component re-renders and the browser re-paints the view, our effect will be invoked and the profile state will be synchronized with the result of our API request.
If we return a function from useEffect
,
Cleanup functions are mostly used for avoiding the memory leaks by such as timers, web socket subscriptions or unbind the event listeners from event.
React.useEffect(() => {
return () => {
// invoked right before invoking
// the new effect on a re-render AND
// right before removing the component
// from the DOM
};
});
Ex-1,
import React from "react";
import ReactDOM from "react-dom";
const WindowResizer = () => {
const [width, setWidth] = React.useState(window.innerWidth);
React.useEffect(() => {
console.log(`In Effect WindowResizer`);
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return function () {
console.log(`Cleaning up Effect for WindowResizer`);
window.removeEventListener("resize", handleResize);
};
}, []);
console.log(`Rendering WindowResizer`);
return <>{<h1>{width}</h1>}</>;
};
const ManageDisplay = () => {
const [toggle, setToggle] = React.useState(true);
return (
<>
<button onClick={(e) => setToggle((a) => !a)}>toggle</button>
{toggle && <WindowResizer />}
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<ManageDisplay />);
Ex-2, In this example, when we click toggle, component removed from DOM and timer cleaned up. no more time prints the message after it removed from DOM.
import React from "react";
import ReactDOM from "react-dom";
const RepeatMessage = ({ message }) => {
React.useEffect(() => {
console.log(`In Effect RepeatMessage`);
const timerId = setInterval(() => {
console.log(message);
}, 2000);
return () => {
console.log(`Cleaning up Effect - RepeatMessage timer ${timerId}`);
clearInterval(timerId);
};
}, [message]);
console.log(`Rendering RepeatMessage`);
return <div>Look at the console, Bruh!</div>;
};
const ManageDisplay = () => {
const [toggle, setToggle] = React.useState(true);
return (
<>
<button onClick={(e) => setToggle((a) => !a)}>toggle</button>
{toggle && <RepeatMessage message={"Hello, Cleany!!"} />}
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<ManageDisplay />);
Note: Try same example by excluding the cleanup function and click on toggle, though it removed the component from DOM, timer still in memory and printing the message 😲😲.
Example for memory leak
Ex-3
import React from "react";
import ReactDOM from "react-dom";
const Profile = ({ username }) => {
const [profile, setProfile] = React.useState(null);
React.useEffect(() => {
console.log(`Running Effect for : ${username}`);
const abortController = new AbortController();
const opts = { signal: abortController.signal };
const getGithubProfile = () => {
fetch(`https://api.github.com/users/${username}`, opts)
.then((res) => res.json())
.then((data) => {
setProfile(data);
})
.catch((error) => {
if (error.name == "AbortError") {
console.log(`Running cleanup - abort the API for : ${username}`);
console.log("request was cancelled");
}
});
};
getGithubProfile(username);
return () => {
console.log();
abortController.abort();
};
}, [username]);
console.log("Rendering Profile");
if (profile === null) {
return <p>Loading...</p>;
}
return (
<>
<p>{profile.name}</p>
<p>{profile.blog}</p>
<p>{profile.location}</p>
</>
);
};
const ProfileContainer = () => {
const [user, setUser] = React.useState("ry");
return (
<>
<button onClick={(e) => setUser("ry")}>ry</button>
<button onClick={(e) => setUser("tj")}>tj</button>
<Profile username={user} />
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<ProfileContainer />);
The Mental Model
Step by step mental model for above code goes here
Ex-4
Tweet post component - limit the char length to 240 & show the remaining count in document title.
import React from "react";
import ReactDOM from "react-dom";
const App = () => {
const maxChars = 20;
const [chars, setChars] = React.useState("");
React.useEffect(() => {
document.title = `${maxChars - chars.length} chars left`;
}, [chars]);
return (
<>
<textarea value={chars} onChange={(e) => setChars(e.target.value)} />
<br />
<button disabled={chars.length === 0 || chars.length > 20}>Tweet</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Ex-5 Wait component.
The purpose of Wait is to render the ui
prop after delay
seconds. Before delay
seconds, it should render placeholder
.
import React from "react";
import ReactDOM from "react-dom";
const Wait = ({ delay = 1000, placeholder, ui }) => {
const [show, setShow] = React.useState(false);
React.useEffect(() => {
const timer = setTimeout(() => {
setShow(true);
}, delay);
return () => clearTimeout(timer);
}, [delay]);
return show === true ? ui : placeholder;
};
const App = () => {
return (
<div className="App">
<Wait
delay={3000}
placeholder={<p>Waiting...</p>}
ui={<p>This text should appear after 3 seconds.</p>}
/>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
useLayoutEffect
In general, you should use useEffect().
useLayoutEffect()
works like useEffect()
, the only difference being that it’s invoked before React is done painting all the DOM nodes of a render.useEffect()
is asynchronous & called after DOM mutation is calculated and after browser engine paints the DOM to browser UI.useLayoutEffect()
is synchronous and called after DOM mutation is calculated and before browser engine paints the DOM to browser UI.useLayoutEffect()
is called sooner, we can recalculate and rerender and the user sees only the last render. Otherwise (if we use useEffect), they see the initial render first, then the second render. Depending on how complicated the layout use, users may perceive a flicker between the two renders.Ex,
We are setting the width of table with dynamically in both the effects.
useEffect
- we see two renders, it’s flickering and second render is the final UI. Two renders because, useEffect ran after DOM painting and after that,we changed the width, so second renderuseLayoutEffect
- We see only one render and no flickering. One render because, useLayoutEffect ran before DOM painting and we changed the width here only, so it took that also for consideration and painted DOM with new width change.import React from "react";
import ReactDOM from "react-dom";
function Example({ layout }) {
if (layout === null) {
return null;
}
if (layout) {
React.useLayoutEffect(() => {
const table = document.getElementsByTagName("table")[0];
table.width = "250px";
}, []);
} else {
React.useEffect(() => {
const table = document.getElementsByTagName("table")[0];
table.width = "250px";
}, []);
}
return (
<table width="500px" border="1">
<thead>
<tr>
<th>Random</th>
</tr>
</thead>
<tbody>
{Array.from(Array(10000)).map((_, idx) => (
<tr key={idx}>
<td width={Math.random() * 800}>{Math.random()}</td>
</tr>
))}
</tbody>
</table>
);
}
function App() {
const [layout, setLayout] = React.useState(null);
return (
<div>
<button onClick={() => setLayout(false)}>useEffect</button>{" "}
<button onClick={() => setLayout(true)}>useLayoutEffect</button>{" "}
<button onClick={() => setLayout(null)}>clear</button>
<Example layout={layout} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Only call Hooks from the top-level of a function component or a custom Hooks.
Means,
Ex,
function Counter() {
// from the top level function component
const [count, setCount] = React.useState(0);
if (count % 2 === 0) {
// not from the top level
React.useEffect(() => {});
}
const handleIncrement = () => {
setCount((c) => c + 1);
// not from the top level
React.useEffect(() => {});
};
}
function useWindow() {
// from the top level of a custom Hook
const [width, setWidth] = React.useState(0);
}
class Counter extends React.Component {
render() {
// not from inside a Class component
const [count, setCount] = React.useState(0);
}
}
function getUser() {
// not from inside a normal function
const [user, setUser] = React.useState(null);
}
The reason for this rule is because React relies on the call order of Hooks to keep track of internal state and references. If the Hooks aren’t called consistently in the same order across renders, React can’t do that.
use
and that may call other Hooks.What problem are these custom hooks are solving?
import React from "react";
import ReactDOM from "react-dom";
const styles = {
container: {
position: "relative",
display: "flex",
},
tooltip: {
boxSizing: "border-box",
position: "absolute",
width: "200px",
bottom: "100%",
left: "40%",
marginLeft: "-80px",
borderRadius: "3px",
backgroundColor: "hsla(0, 0%, 20%, 0.9)",
padding: "7px",
marginBottom: "5px",
color: "#fff",
textAlign: "center",
fontSize: "14px",
},
};
const getData = async (path) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/${path}`);
return res.json();
};
const Loading = () => {
return <div>Yo, loading</div>;
};
const App = () => {
return (
<>
<Author />
<Post />
<br />
<i>Note: Mouseover on username and title, will see the tooltips</i>
</>
);
};
const Author = () => {
const [author, setAuthor] = React.useState(null);
const [hovering, setHovering] = React.useState(false);
React.useEffect(() => {
getData("users/1").then(setAuthor);
}, []);
const mouseOver = () => {
setHovering(true);
};
const mouseOut = () => {
setHovering(false);
};
if (author == null) return <Loading />;
return (
<>
<h4>Author:</h4>
<div
style={styles.container}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
>
{hovering && <span style={styles.tooltip}>{author.name}</span>}
Username: {author.username}
</div>
</>
);
};
const Post = () => {
const [post, setPost] = React.useState(null);
const [hovering, setHovering] = React.useState(false);
React.useEffect(() => {
getData("posts/1").then(setPost);
}, []);
const mouseOver = () => {
setHovering(true);
};
const mouseOut = () => {
setHovering(false);
};
if (post == null) return <Loading />;
return (
<div>
<h3>Post:</h3>
<div
style={styles.container}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
>
{hovering && <span style={styles.tooltip}>{post.body}</span>}
Title: {post.title}
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
If we observe above code, everything works fine, but
We have made a re-usable UI component Loading
. Since it’s static message, it has been abstracted away. Its good practice too. We can pass properties that can be rendered dynamically. This is primary advantage of component based UI library such as React.
What about non visual logic or stateful logic like mouseOver
, mouseOut
, hovering
& setHovering
. These are duplicated across the components.
const [hovering, setHovering] = React.useState(false);
const mouseOver = () => {
setHovering(true);
};
const mouseOut = () => {
setHovering(false);
};
How to share non-visual logic or stateful logic between components?
We can create a custom hook to share these non visual logic or stateful logic. For an example,
const useHover = () => {
const [hovering, setHovering] = React.useState(false);
const mouseOver = () => {
setHovering(true);
};
const mouseOut = () => {
setHovering(false);
};
return [hovering, mouseOver, mouseOut];
};
Now,
// Replace
const [hovering, setHovering] = React.useState(false);
//with,
const [hovering, mouseOver, mouseOut] = useHover();
Now, everything should work as earlier, except, now we have shared the non-visual the logic between the components.
More use cases:
Ex-1, Implement the useWait
custom Hook. useWait
should return a boolean that changes from false to true after delay seconds.
import React from "react";
import ReactDOM from "react-dom";
const useWait = (delay) => {
const [show, setShow] = React.useState(false);
React.useEffect(() => {
const timer = setTimeout(() => {
setShow(true);
}, delay);
return () => clearTimeout(timer);
}, [delay]); // If we don't mention the dependency, then use Effect in useWait will be called on every re-render.
return show;
};
const Wait = ({ delay, placeholder, ui }) => {
const show = useWait(delay);
return show === true ? ui : placeholder;
};
const App = () => {
return (
<div className="App">
<Wait
delay={3000}
placeholder={<p>Waiting...</p>}
ui={<p>This text should appear after 3 seconds.</p>}
/>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Ex-2, Create a useWindowDimensions
custom Hook. It should return an object with a width
property that represents the current width of the window and a height
property which represents the current height.
import React from "react";
import ReactDOM from "react-dom";
const useWindowDimensions = () => {
const [width, setWidth] = React.useState(window.innerWidth);
const [height, setHeight] = React.useState(window.innerHeight);
const handleResize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
};
React.useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
});
return [width, height];
};
const Comp1 = () => {
const [width, height] = useWindowDimensions();
return (
<>
{Comp1.name}
<br />
Width: {width}
<br />
Height: {height}
</>
);
};
const Comp2 = () => {
const [width, height] = useWindowDimensions();
return (
<>
{Comp2.name}
<br />
Width: {width}
<br />
Height: {height}
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(
<>
<Comp1 />
<hr></hr>
<Comp2 />
</>
);
Ex-3, useFetch
A lot useful custom hooks are written and maintained in the React community.
Check below links.
The reduce()
method executes a reducer function (that we provide) on each element of the array, resulting in a single output value.
const nums = [2, 4, 6];
const initialState = 0;
const reducerFn = (stateOrAccumulator, value) => {
return stateOrAccumulator + value;
};
const total = nums.reduce(reducerFn, initialState);
console.log(total); // 12
How reduce()
works, initialState
will copied to stateOrAccumulator
initially.
reducerFn | stateOrAccumulator | value | return value |
---|---|---|---|
1st invocation | 0 | 2 | 2 |
2nd invocation | 2 | 4 | 6 |
3rd invocation | 6 | 6 | 12 |
Final output is 12
What if nums is a collection of user actions that happened over time? Then, whenever a new user action occurred, we could invoke the reducer function which would get us the new state.
The API for useReducer
is similar to JavaScript reduce
however, there’s one difference.
Instead of just returning the state, we need a way for user actions to invoke our reducer function. Because of this, useReducer
returns an array with the first element being the state and the second element being a dispatch function which when called, will invoke the reducer function.
The signature,
const reducerFn = (state, value) => {};
const [state, dispatch] = React.useReducer(reducerFn, initialState);
When dispatch
invoked/called, whatever we pass to dispatch will be passed as the second argument (value) to the reducer. The first argument (state
) will be passed implicitly by React and will be whatever the initialState
on first call and previous state on subsequent call.
Ex-1,
import React from "react";
import ReactDOM from "react-dom";
const reducer = (state, value) => {
return state + value;
};
const Counter = () => {
const [count, dispatch] = React.useReducer(reducer, 0);
return (
<>
<h1>{count}</h1>
<button onClick={() => dispatch(1)}>+</button>
<button onClick={() => dispatch(-1)}>-</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Counter />);
How to Reset the Counter, with the current implementation; it’s not possible. What if instead of dispatching the value directly, we dispatch the type of action that occurred? That way, based on the type of action, our reducer can decide how to update the state.
Let’s add reset feature.
import React from "react";
import ReactDOM from "react-dom";
const reducer = (state, action) => {
if (action.type === "increment") {
return state + action.step;
} else if (action.type === "decrement") {
return state - action.step;
} else if (action.type === "reset") {
return 0;
} else {
throw new Error(`This action type isn't supported.`);
}
};
const Counter = () => {
const [count, dispatch] = React.useReducer(reducer, 0);
return (
<>
<h1>{count}</h1>
<button onClick={() => dispatch({ type: "increment", step: 1 })}>
+
</button>
<button onClick={() => dispatch({ type: "decrement", step: 2 })}>
-
</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Counter />);
Fundamentally, useState and useReducer both allow us to add state to function components.
Let’s build a same app using using useState
& useReducer
separately.
Ex-2 useState
import React from "react";
import ReactDOM from "react-dom";
const newUser = (user) => {
return Promise.resolve(console.log(user));
};
const Redirect = () => {
return <h1>Dashboard</h1>;
};
const Loading = () => {
return <p>Loading...</p>;
};
const Register = () => {
const [username, setUsername] = React.useState("");
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState("");
const [registered, setRegistered] = React.useState(false);
const handleSubmit = (e) => {
e.preventDefault();
setLoading(true);
setError("");
newUser({ username, email, password })
.then(() => {
setLoading(false);
setError("");
setRegistered(true);
})
.catch((error) => {
setLoading(false);
setError(error);
});
};
if (registered === true) {
return <Redirect to="/dashboard" />;
}
if (loading === true) {
return <Loading />;
}
return (
<>
{error && <p>{error}</p>}
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="email"
onChange={(e) => setEmail(e.target.value)}
value={email}
/>
<input
type="text"
placeholder="username"
onChange={(e) => setUsername(e.target.value)}
value={username}
/>
<input
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
type="password"
/>
<button type="submit">Submit</button>
</form>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Register />);
The above code is pretty imperative approach to solving the problem. We’re conforming to the operational model of the machine by describing how we want to accomplish the task.
Ex-3 useReducer
import React from "react";
import ReactDOM from "react-dom";
const newUser = (user) => {
return Promise.resolve(console.log(user));
};
const Redirect = () => {
return <h1>Dashboard</h1>;
};
const Loading = () => {
return <p>Loading...</p>;
};
const registerReducer = (state, action) => {
if (action.type === "login") {
return {
...state,
loading: true,
error: "",
};
} else if (action.type === "success") {
return {
...state,
loading: false,
error: "",
registered: true,
};
} else if (action.type === "error") {
return { ...state, loading: false, error: action.error };
} else if (action.type === "input") {
return {
...state,
[action.name]: action.value,
};
} else {
throw new Error(`This action type isn't supported.`);
}
};
const initialState = {
username: "",
email: "",
password: "",
loading: false,
error: "",
registered: false,
};
const Register = () => {
const [state, dispatch] = React.useReducer(registerReducer, initialState);
const handleSubmit = (e) => {
e.preventDefault();
dispatch({ type: "login" });
newUser({
username: state.username,
email: state.email,
password: state.password,
})
.then(() => dispatch({ type: "success" }))
.catch((error) => dispatch({ type: "error", error }));
};
if (state.registered === true) {
return <Redirect to="/dashboard" />;
}
if (state.loading === true) {
return <Loading />;
}
return (
<>
{state.error && <p>{state.error}</p>}
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="email"
onChange={(e) =>
dispatch({
type: "input",
name: "email",
value: e.target.value,
})
}
value={state.email}
/>
<input
type="text"
placeholder="username"
onChange={(e) =>
dispatch({
type: "input",
name: "username",
value: e.target.value,
})
}
value={state.username}
/>
<input
placeholder="password"
onChange={(e) =>
dispatch({
type: "input",
name: "password",
value: e.target.value,
})
}
value={state.password}
type="password"
/>
<button type="submit">Submit</button>
</form>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Register />);
Instead of describing how we want to accomplish the task, above code snippet using useReducer describe what we’re trying to accomplish. It’s declarative approach.
useState
and useReducer
both allow us to add state to function components. useReducer
offers a bit more flexibility since it allows you to decouple how the state is updated from the action that triggered the update - typically leading to more declarative state updates.
If different pieces of state update independently from one another (hovering, selected, etc.), useState should work fine. If your state tends to be updated together or if updating one piece of state is based on another piece of state, go with useReducer.
In other words, If we have multiple pieces of interrelated state, consider using a reducer to clearly define the actions that can change the state.
New Render, Same Value or In other words, New value, No re-render
import React from "react";
import ReactDOM from "react-dom";
const StopWatch = () => {
const [count, setCount] = React.useState(0);
let id = undefined;
const stop = () => {
clearInterval(id);
};
console.log(id);
React.useEffect(() => {
id = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
console.log(id);
return stop;
}, []);
return (
<>
<h1>{count}</h1>
<button onClick={stop}>Stop</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<StopWatch />);
Since we have stop
function at the component level. We had to move the declaration of id
up to the main scope and then initialize it with the id when the effect runs. so, it will be available at functional level to stop the count.
Clicking on the Stop button will not stop the count, why??
The reason for this is because id
doesn’t persist across renders. As soon as our count state variable changes, React will re-render Counter , re-declaring id
setting it back to undefined
. Look at the console logs.
Now,
Q. How to preserve values between renders and to trigger a re-render whenever you change the value?
A. useState
In our case, we don’t need to update the value, we just need to persist/preserve the value between the renders. So, let’s create a custom hook.
const usePersistentValue = (initialValue) => {
return React.useState({
current: initialValue,
})[0];
};
Full ex,
import React from "react";
import ReactDOM from "react-dom";
const usePersistentValue = (initialValue) => {
return React.useState({
current: initialValue,
})[0];
};
const StopWatch = () => {
const [count, setCount] = React.useState(0);
let id = usePersistentValue(undefined);
const stop = () => {
clearInterval(id.current);
};
console.log(id);
React.useEffect(() => {
id.current = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
console.log(id);
return stop;
}, []);
return (
<>
<h1>{count}</h1>
<button onClick={stop}>Stop</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<StopWatch />);
Now instead of id
being re-declared on every render, because it’s really a value coming from useState , React will persist it across renders.
The ability to persist a value across renders without causing a re-render is so fundamental that React comes with a built-in Hook for it called useRef
.
Replace,
let id = usePersistentValue(undefined);
with,
let id = React.useRef(undefined);
We should get the same result as earlier. useRef follows the same API we created earlier. It accepts an initial value as its first argument and it returns an object that has a current
property (which will initially be set to whatever the initial value was). From there, anything we add to current
will be persisted across renders.
The most popular use case for useRef
is getting access to DOM nodes to have uncontrolled form elements.
import React from "react";
import ReactDOM from "react-dom";
const Form = () => {
const emailRef = React.useRef();
const passwordRef = React.useRef();
const handleSubmit = (e) => {
e.preventDefault();
const email = emailRef.current.value;
const password = passwordRef.current.value;
console.log(email, password);
};
const reset = () => {
emailRef.current.value = "";
passwordRef.current.value = "";
};
return (
<>
<input placeholder="name" type="text" ref={emailRef} />
<input placeholder="password" type="text" ref={passwordRef} />
<hr />
<button onClick={() => emailRef.current.focus()}>
Focus Email Input
</button>
<button onClick={() => passwordRef.current.focus()}>
Focus Password Input
</button>
<hr />
<button onClick={handleSubmit}>Submit</button>
<button onClick={reset}>Reset</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Form />);
If we pass the “return value you from useRef
as a ref
prop on any React element, React will set the current
property to the corresponding DOM. This allows you to do things like grab input values or set focus.
If we observe carefully, we have achieved interactive form without using useState
& that means, without re-renders. Our ref’s will always have latest data.
useState
or useReducer
.useRef
.Bypassing the Props
(A). Let’s say, we had an app with the following architecture, each box representing a different component.
(B). Assume, we had a piece of state that was needed throughout various levels of our application - yellow boxes.
(C). Solution for this problem is to move that state up to the nearest parent component and then pass it down via props. It called state lifting.
Problem,
If we observe red box component, its just passing through the props down, really not using it. When passing props through intermediate components can become overly redundant or unmanageable, if we have more intermediate components. It’s called Prop Drilling.
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Let’s build an app that works in English as well as in Kannada and have a button that when it’s clicked, can toggle the text of our entire application between English and Kannada.
The Mental Model
We need a way to declare the data & that should be available throughout our component tree (app). For an example, that data is Kannada & English values.
Find a way for any component in the component tree to subscribe to the data changes, so accordingly it can render UI.
To create a new context, use React.createContext
function.
import React from "react";
import ReactDOM from "react-dom";
const LangContext = React.createContext("english");
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<></>);
LangContext
, has two properties, both of which are React components, Provider
, and Consumer
.
It accepts a value
prop which is the data that we want available to any of its children
who need to consume it.
import React from "react";
import ReactDOM from "react-dom";
const LangContext = React.createContext();
const App = () => {
const [lang, setLang] = React.useState("english");
return (
<LangContext.Provider value={lang}>
<Header />
</LangContext.Provider>
);
};
const Header = () => {
return <NavBar />;
};
const NavBar = () => {
return <></>;
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Now, any component in our component tree that needs the value lang have the option to subscribe to it using <LangContext.Consumer>
OR useContext(LangContext)
Now in our example, because we passed lang
as the value
prop to LangContext Provider
, we can get access to it by passing LangContext.Consumer
a render prop.
import React from "react";
import ReactDOM from "react-dom";
const LangContext = React.createContext();
const App = () => {
const [lang, setLang] = React.useState("english");
return (
<LangContext.Provider value={lang}>
<Header />
</LangContext.Provider>
);
};
const Header = () => {
return <NavBar />;
};
const NavBar = () => {
return (
<LangContext.Consumer>
{(selectedLang) => {
return selectedLang === "english" ? (
<EnglishNavigation />
) : (
<KannadaNavigation />
);
}}
</LangContext.Consumer>
);
};
const EnglishNavigation = () => {
return (
<>
<ul>
<li>
<a href="#;">Home</a>
</li>
<li>
<a href="#;">Products</a>
</li>
<li>
<a href="#;">Services</a>
</li>
</ul>
<button>ಕನ್ನಡ</button>
</>
);
};
const KannadaNavigation = () => {
return (
<>
<ul>
<li>
<a href="#;">ಮನೆ</a>
</li>
<li>
<a href="#;">ಉತ್ಪನ್ನಗಳು</a>
</li>
<li>
<a href="#;">ಸೇವೆಗಳು</a>
</li>
</ul>
<button>English</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Clicking on the button won’t work as it not updating the lang
yet.
lang
.import React from "react";
import ReactDOM from "react-dom";
const LangContext = React.createContext();
const App = () => {
const [lang, setLang] = React.useState("english");
const toggleLang = () => {
setLang((lang) => (lang === "english" ? "kannada" : "english"));
};
return (
<LangContext.Provider value=>
<Header />
</LangContext.Provider>
);
};
const Header = () => {
return <NavBar />;
};
const NavBar = () => {
return (
<LangContext.Consumer>
{(langObj) => {
return langObj.lang === "english" ? (
<EnglishNavigation toggle={langObj.toggleLang} />
) : (
<KannadaNavigation toggle={langObj.toggleLang} />
);
}}
</LangContext.Consumer>
);
};
const EnglishNavigation = ({ toggle }) => {
return (
<>
<ul>
<li>
<a href="#;">Home</a>
</li>
<li>
<a href="#;">Products</a>
</li>
<li>
<a href="#;">Services</a>
</li>
</ul>
<button onClick={toggle}>ಕನ್ನಡ</button>
</>
);
};
const KannadaNavigation = ({ toggle }) => {
return (
<>
<ul>
<li>
<a href="#;">ಮನೆ</a>
</li>
<li>
<a href="#;">ಉತ್ಪನ್ನಗಳು</a>
</li>
<li>
<a href="#;">ಸೇವೆಗಳು</a>
</li>
</ul>
<button onClick={toggle}>English</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Final output, Clicking on button changes the language.
This works, but as always the render-props syntax is a little funky. The problem gets worse if you have multiple context values you need to grab (Wrapper Hell).
Example,
export default function Nav() {
return (
<AuthedContext.Consumer>
{({ authed }) =>
authed === false ? (
<Redirect to="/login" />
) : (
<LangContext.Consumer>
{({ lang, toggleLang }) =>
lang === "english" ? (
<EnglishNavigation toggle={toggleLang} />
) : (
<KannadaNavigation toggle={toggleLang} />
)
}
</LocaleContext.Consumer>
)
}
</LangContext.Consumer>
);
}
So, whats the solution?
It takes in a Context object as its first argument and returns whatever was passed to the value
prop of the nearest Provider
component or defaultValue
.
export default function Nav() {
const { authed } = React.useContext(AuthedContext);
if (authed === false) {
return <Redirect to="/login" />;
}
const { lang, toggleLang } = React.useContext(LangContext);
return locale === "english" ? (
<EnglishNavigation toggle={toggleLang} />
) : (
<KannadaNavigation toggle={toggleLang} />
);
}
So, our app can consume Context value as below.
ONLY CHANGE AT THE CONSUMER LEVEL NOT AT THE PROVIDER.
Replace NavBar
fn with below implementation React.useContext(LangContext)
function NavBar() {
const { lang, toggleLang } = React.useContext(LangContext);
return lang === "english" ? (
<EnglishNavigation toggle={toggleLang} />
) : (
<KannadaNavigation toggle={toggleLang} />
);
}
Everything works fine now.
We have a performance issue here.
Just like React re-renders with prop
changes, whenever the data passed to value
changes, React will re-render every component which used Consumer
or useContext
subscribe to that data.
The way in which React knows if the data changes is by using “reference identity” or comparing the location or reference or memory location address (Like, oldObject === newObject).
value=
value
every time that App re-renders with some other state or props changes (these can be related to context or completely unrelated to context).value
has changed, it’ll always think it has since we’re always passing in a new object literal for every re-render.lang
or toggleLang
didn’t change.To fix this, instead of passing a new object literal to value every time, we want to give it a reference to an object it already knows about.
useMemo
Replace App
fn with below code which uses useMemo
const App = () => {
const [lang, setLang] = React.useState("english");
const toggleLang = () => {
setLang((lang) => (lang === "english" ? "kannada" : "english"));
};
const value = React.useMemo(() => {
return { lang, toggleLang };
}, [lang]);
return (
<LangContext.Provider value={value}>
<Header />
</LangContext.Provider>
);
};
Whenever we render a Consumer
component, it gets its value from the value
prop of the nearest Provider
component of the same Context object.
What if there isn’t a parent Provider
of the same Context object? In that case, it’ll get its value from the first argument that was passed to createContext when the Context object was created.
const LangContext = React.createContext("kannada"); // We can pass anything like obj, strings, array etc.
Now, if we use <LangContext.Consumer>
without previously rendering a <LangContext.Provider>
or without Provider
as a wrapper, the value passed to Consumer will be kannada, the default value.
import React from "react";
import ReactDOM from "react-dom";
const LangContext = React.createContext("kannada");
const App = () => {
return <Header />;
};
const Header = () => {
return <NavBar />;
};
const NavBar = () => {
return (
<LangContext.Consumer>
{(lang) => {
console.log(lang); // prints => kannada
return <></>;
}}
</LangContext.Consumer>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
NOTE :
Example, LangContext, AuthContext, ThemeContext etc.
// LangContext.js
import React from "react";
const LangContext = React.createContext();
export { LangContext };
Ex-1 , find what the problem with below.
import React from "react";
import ReactDOM from "react-dom";
const Movie = ({ title }) => {
console.log(`Movie component rendered with: ${title}`);
return (
<div>
Now watching - {title}
<hr />
<div
style=
></div>
</div>
);
};
const Makers = ({ title }) => {
console.log(`Makers component rendered with: ${title}`);
return (
<div>
<br />
Cast and crew of the movie : {title}
</div>
);
};
const App = () => {
const [movieIndex, setMovieIndex] = React.useState();
const [rating, setRating] = React.useState(0);
const movies = ["Into the Wild", "V for Vendetta", "The Man from Earth"];
const movie = movies[movieIndex];
console.log("App component rendered");
return (
<>
<h3>Amazon Prem's </h3>
<ol>
{movies.map((m, index) => (
<li key={m} onClick={() => setMovieIndex(index)}>
{m}
</li>
))}
</ol>
{movie && <Movie title={movie} />}
{movie && <Makers title={movie} />}
<hr />
<button onClick={() => setRating((r) => r + 1)}>Rate our website</button>
<p>Current rating: {rating}</p>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
It has performance issues, look at the console.
While user clicking on rating button, we see Movie
and Makers
components are getting re-rendered unnecessarily.
When deciding to update DOM, React first renders your component, then compares the result with the previous render. If the render results are different, then only React updates the DOM.
So, in our condition, though react re-renders component, it will not update the DOM.
Current vs previous render results comparison is fast. But we can speed up the process under some circumstances.
When a component is wrapped in React.memo(), React renders the component and memoizes the result. Before the next render, if the new props are the same, React reuses the memoized result skipping the next rendering.
Solution,
Create the memoized version of components and use it like below,
const MemoizedMovie = React.memo(Movie);
const MemoizedMakers = React.memo(Makers);
// Update App component with below
{
movie && <MemoizedMovie title={movie} />;
}
{
movie && <MemoizedMakers title={movie} />;
}
Now, React reuses the memoized content as long as title
prop are the same between renderings.
On re-renders, React will perform a shallow comparison (===) between the previous props and the new props - if the props haven’t changed, React will skip rendering the component and reuse memoized last rendered result.
Lets extend the app with feature - play next movie
play next movie is added.
Ex-2
import React from "react";
import ReactDOM from "react-dom";
const Movie = ({ title, playNextMovie }) => {
console.log(`Movie component rendered with: ${title}`);
return (
<div>
Now watching - {title}
<hr />
<div
style=
></div>
<button onClick={playNextMovie}>Next</button>
</div>
);
};
const Makers = ({ title }) => {
console.log(`Makers component rendered with: ${title}`);
return (
<div>
<br />
Cast and crew of the movie : {title}
</div>
);
};
const MemoizedMovie = React.memo(Movie);
const MemoizedMakers = React.memo(Makers);
const App = () => {
const [movieIndex, setMovieIndex] = React.useState();
const [rating, setRating] = React.useState(0);
const movies = ["Into the Wild", "V for Vendetta", "The Man from Earth"];
const movie = movies[movieIndex];
const playNextMovie = () => {
setMovieIndex((m) => (m + 1) % movies.length);
};
console.log("App component rendered");
return (
<>
<h3>Amazon Prem's </h3>
<ol>
{movies.map((m, index) => (
<li key={m} onClick={() => setMovieIndex(index)}>
{m}
</li>
))}
</ol>
{movie && <MemoizedMovie title={movie} playNextMovie={playNextMovie} />}
{movie && <MemoizedMakers title={movie} />}
<hr />
<button onClick={() => setRating((r) => r + 1)}>Rate our website</button>
<p>Current rating: {rating}</p>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Click on Next button,everything work as expect.
Open console, now, click on names of the movie and next button all good.
Now, click on rating
button, 🤯🤯 what? why??? Movie
component getting re-rendered unnecessarily, did’nt we fix it using React.memo??
Problem,
Movie
component, we’re passing function as playNextMovie
as props.rating
button, it causes re-render of App
component. This means for every render, we’re creating a brand new function in memory.React.memo
compares the previous playNextMovie
prop with the new playNextMovie
prop, even though they appear the same, they’re compared by their references which will always be different.Makers
components is not getting re-rendered.2 solutions,
React.memo
to only compare the title
prop and ignore the playNextMovie
props.playNextMovie
prop the same reference across renders using React.useCallback()
Customize React.memo
for custom equality check of props
Ex-3
// Replace this code
const MemoizedMovie = React.memo(Movie);
// With this
const MemoizedMovie = React.memo(
Movie,
(prevProps, nextProps) => prevProps.title === nextProps.title
);
Lets solve the problem with useCallback.
useCallback
returns a memoized callback.useCallback
won’t be re-created on subsequent re-renders.Ex,
const memoizedCallback = React.useCallback(() => doSomething(a, b), [a, b]);
Now,
// Reintroduce the issue by placing below code
const MemoizedMovie = React.memo(Movie);
// Replace current implementation playNextMovie
const playNextMovie = () => {
setMovieIndex((m) => (m + 1) % movies.length);
};
// With useCallback pattern
const playNextMovie = React.useCallback(() => {
setMovieIndex((m) => (m + 1) % movies.length);
}, []);
Now check the console, everything works fine.
playNextMovie
prop and creating a brand new function on every render, we can utilize useCallback
to create one function on the initial render, and reuse it on subsequent renders.React.memo
compares the previous playNextMovie
prop with the new playNextMovie
prop,the reference will be the same and the identity operator (===) will work as expected.What if, instead of memoizing at the component level using React.memo
, we memoize the expensive calculations themselves? React.useMemo()
useMemo
takes two arguments, a function and an array of values that the function depends on.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo(compute, dependencies)
invokes computeExpensiveValue
, memoizes the calculation result, and returns it to the component.useMemo()
doesn’t invoke compute but returns the memoized value.useMemo()
invokes compute, memoizes the new value, and returns it.Ex-1, Problem.
import React from "react";
import ReactDOM from "react-dom";
const CalcFact = () => {
const [number, setNumber] = React.useState(1);
const [_, setInc] = React.useState(0);
const factorial = factorialOf(number);
const onChange = (event) => {
setNumber(Number(event.target.value));
};
return (
<div>
Factorial of
<input type="number" value={number} onChange={onChange} /> is
{factorial}
<button onClick={() => setInc(Math.random())}>Cause re-render</button>
</div>
);
};
const factorialOf = (n) => {
console.log("factorialOf(n) called!");
return n <= 0 ? 1 : n * factorialOf(n - 1);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<CalcFact />);
Everything works here but with performance issue.
factorialOf
would be called, we see that in console, thats correct.inc
state value is updated. Updating inc
state value triggers <CalcFact />
re-rendering. But, as a secondary effect, during re-rendering the factorial is recalculated again — ‘factorialOf(n) called!’ is logged to console.How to solve this issue? Let’s memoize the factorialOf
fn.
// Replace normal function
const factorial = factorialOf(number);
// With memoized fn
const factorial = React.useMemo(() => {
factorialOf(number);
}, [number]);
factorialOf(n) called!
is logged to console. That’s expected.Cause re-render
button, factorialOf(n) called!
isn’t logged to console because React.useMemo(() => factorialOf(number), [number])
returns the memoized factorial calculation.
useMemo
!=useRef
. To persist the values across re-renders, always use useRef.- React treats
useMemo
as a performance hint rather than a guarantee. This means that React may choose to forget previously memoized values in some cases.
useState
hook allows us to add state to function components and persist value between renders, trigger re-render.
useRef
hook persist value between renders, cause NO re-render.
useEffect
Let us add side effects to our function components and run after render.
useReducer
is a useState in reducer pattern that takes a collection as input and returns a single value as output.
useMemo
hook allow us memoize value between renders.
useCallback
hook helps us persists referential equality between renders for functions.
useContext
- Context lets us pass data to any part of your component tree without passing it down through individual components & useContext
can consume anywhere down in the component tree.
React.memo
is a Higher-order component (HOC) that lets you skip re-rendering a component if its props haven’t changed.
Custom Hooks
- In order to share non-visual logic or stateful logic, we had to rely on patterns like Higher-order components or Render-props. Now, we can accomplish the same thing by building our own custom Hooks.
Problem statement,
index.jsx
import React from "react";
import ReactDOM from "react-dom";
import { Synopsis } from "./Synopsis";
import Plot from "./Plot";
const Movie = () => {
const [showPlot, setShowPlot] = React.useState(false);
return (
<>
<h2>Inception</h2>
<Synopsis />
<button onClick={() => setShowPlot((prevState) => !prevState)}>
{showPlot ? "Hide" : "Show full plot.."}
</button>
{showPlot && <Plot />}
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Movie />);
Synopsis.jsx
import React from "react";
const Synopsis = () => {
return (
<p>
A thief who steals corporate secrets through the use of dr eam-sharing
technology is given the inverse task of planting an idea i nto the mind of
a C.E.O.
</p>
);
};
export { Synopsis };
Plot.jsx
import React from "react";
const Plot = () => {
const styles = {
container: {
width: "75%",
marginTop: "15px",
},
movieGif: {
float: "left",
marginRight: "21px",
},
};
return (
<div style={styles.container}>
<img
src="https://i.gifer.com/TZQY.gif"
alt="inception"
height="300"
width="250"
style={styles.movieGif}
/>
Dom Cobb is a skilled thief, the absolute best in the dangerous art of extraction,
stealing valuable secrets from deep within the subconscious during the dream
state, when the mind is at its most vulnerable. Cobb's rare ability has made
him a coveted player in this treacherous new world of corporate espionage,
but it has also made him an international fugitive and cost him everything
he has ever loved. Now Cobb is bei ng offered a chance at redemption. One last
job could give him his life back but only if he can accomplish the impossible,
inception. Instead of the perfect heist, Cobb and his team of specialists have
to pull off t he reverse: their task is not to steal an idea, but to plant
one. If t hey succeed, it could be the perfect crime. But no amount of careful
planning or expertise can prepare the team for the dangerous enemy that seems
to predict their every move. An enemy that only Cobb could have seen coming.
<hr />
—Warner Bros. Pictures
</div>
);
};
export default Plot;
The app just works fine. But there is a room for improvement in terms of performance. What if have list of 100 movies & each plot component has ever growing content and high pixel assets and etc.
We can load JavaScript (module/code) related to these component lazily Component level Code-Split.
The React.lazy
function lets us render a dynamic import
as a regular component.
Because the import is inside of a function passed to lazy()
, the loading of the component won’t happen until we actually use the component.
Ex,
// SomeComponent must be Default export, like - export default SomeComponent;
// If we use the Named export it fails, like export { SomeComponent }
const LazyComponent = React.lazy(() => import("./SomeComponent"));
const App = () => {
return (
<div>
<LazyComponent />
</div>
);
};
React.lazy
takes a function that must call adynamic import()
.
This must return a Promise which resolves to a module with a default export containing a React component.
If the module containing the OtherComponent
is not yet loaded by the time App renders, we must show some fallback content while we’re waiting for it to load - such as a loading indicator. This is done using the Suspense
component.
const LazyOtherComponent = React.lazy(() => import("./OtherComponent"));
const App = () => {
return (
<div>
<React.Suspense fallback={<div>Loading...</div>}>
<LazyOtherComponent />
</React.Suspense>
</div>
);
};
Now, Movie app can be rewritten like below with lazy loading or code splitting. No change is required at Synopsis.js
& Plot.js
index.js; updated,
import React from "react";
import ReactDOM from "react-dom";
import { Synopsis } from "./Synopsis";
const LazyPlot = React.lazy(() => import("./Plot"));
const Movie = () => {
const [showPlot, setShowPlot] = React.useState(false);
return (
<>
<h2>Inception</h2>
<Synopsis />
<button onClick={() => setShowPlot((prevState) => !prevState)}>
{showPlot ? "Hide" : "Show full plot.."}
</button>
{showPlot && (
<React.Suspense fallback={<div>Loading</div>}>
<LazyPlot />
</React.Suspense>
)}
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Movie />);
Output,
Like this we can lazy load any component which are not needed in initial loading like, Exception handling component, Modal dialog components etc.
React.lazy
currently only supports default exports.
React Router is a declarative, component based, client and server-side routing library for React.
npm i react-router-dom
BrowserRouter
We want to connect our app to the browser’s URL, so import BrowserRouter
and render it around the whole app.
Under the hood, BrowserRouter
uses both the history
library as well as React Context
,
history
library helps React Router keep track of the browsing history of the application using the browser’s built-in history stackReact Context
helps make history available wherever React Router needs it.Route
Route
allow us to map our app’s location to different React components.Dashboard
component whenever a user navigated to the /dashboard
path.With our
Route
elements in this configuration, it’s possible for multiple routes to match on a single URL.
Routes
Routes
as the conductor/controller of our routes. Whenever we have one or more Route
s, we’ll most likely want to wrap them in a Routes
.Routes
job is to understand all of its children Route elements, and intelligently choose which ones are the best to render.Link
<Link>
allows the user to change the URL when they click it.<a>
HTML tag but it does more than just navigation.Ex-1,
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const Topics = () => <h1>Topics</h1>;
const App = () => (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/topics">Topics</Link>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/topics" element={<Topics />} />
</Routes>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Ex-2,
We can also pass an object to Link
. Doing so allows us to add a querystring
via the search
property or pass along any data to the new route via state
.
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter,
Routes,
Route,
Link,
useLocation,
} from "react-router-dom";
const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const Topics = () => {
const loc = useLocation();
return <h4>Topics, with {JSON.stringify(loc)}</h4>;
};
const App = () => (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link
to=
state=
>
Topics
</Link>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/topics" element={<Topics />} />
</Routes>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
/*When we select /topics route,
URL : http://localhost:3000/topics?sort=date
Component output : Topics, with {"pathname":"/topics","search":"?sort=date","hash":"","state":{"fromHome":true},"key":"zlfw60dw"}
*/
To pass data through a Link component to a new route, use Link’s state
prop.
Anytime we pass data along via the state
prop, that data will be available on the location’s state
property, which we can get access to by using the custom useLocation
Hook that comes with React Router.
URL Parameters allow us to declare placeholders for portions of a URL.
Ex, https://www.netflix.com/in/title/81497215
This can represented through the routing like, https://www.netflix.com/in/title/{this part is placeholder - id}
The way we tell React Router that a certain portion of the URL is a placeholder (or URL Parameter), is by using a : in the Route’s path prop.
<Route path="https://www.netflix.com/in/title/:id" element={<Movie />} />
Now the question becomes, how do you access the dynamic portion of the URL – in this case, id
– in the component that’s rendered?
useParams
Hook that returns an object with a mapping between the URL parameter(s) and its value.
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Link,
Routes,
useParams,
} from "react-router-dom";
const getMovie = (id) =>
Promise.resolve(`Here is your movie data with id ${id}`);
const Movie = () => {
const params = useParams();
React.useEffect(() => {
getMovie(params.id).then();
}, [params.id]);
return (
<>
Movie ID is : {params.id}
<hr />
props : <pre>{JSON.stringify(params, null, 3)}</pre>
</>
);
};
const App = () => {
return (
<Router>
<p>
<Link to="/movies/101">Inception</Link>
</p>
<p>
<Link to="/movies/304">Pulp Fiction</Link>
</p>
<p>
<Link to="/movies/420">Kill Bill</Link>
</p>
<hr />
<Routes>
<Route path="movies/:id" element={<Movie />} />
</Routes>
</Router>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
Nested Routes allow the parent Route
to act as wrapper and control the rendering of a child Route
.
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Routes,
Route,
Link,
useParams,
Outlet,
} from "react-router-dom";
const styles = {
border: { border: "2px solid", padding: "10px" },
};
const movies = [
{
name: "V for Vendetta",
id: "vvf",
description:
'In a future British tyranny, a shadowy freedom fighter, known only by the alias of "V", plots to overthrow it with the help of a young woman.',
characters: [
{
name: "V",
id: "v",
dialogues: [
"Beneath this mask there is more than flesh, Beneath this mask there is an idea, Mr. Creedy, and ideas are bulletproof.",
"Remember, remember, the Fifth of November, the Gunpowder Treason and Plot.",
],
},
{
name: "Evey",
id: "evey",
dialogues: ["God is in the rain."],
},
],
},
];
const Character = () => {
const params = useParams();
const movie = movies.find((a) => a.id === params.movieId);
const char = movie.characters.find((c) => c.id === params.charId);
return (
<div style={styles.border}>
<h4>Dialogues</h4>
{char.dialogues.map((d) => {
return <pre key={d}>{d}</pre>;
})}
</div>
);
};
const Movie = () => {
const params = useParams();
const movie = movies.find((a) => a.id === params.movieId);
return (
<div style={styles.border}>
<h2>{movie.name}</h2>
<p>{movie.description}</p>
<h4>Characters</h4>
<ul>
{movie.characters.map((c) => {
return (
<li key={c.id}>
<Link to={`/movies/${params.movieId}/${c.id}`}>{c.name}</Link>
</li>
);
})}
</ul>
<Outlet></Outlet>
</div>
);
};
const Home = () => <h1>Home</h1>;
const App = () => {
return (
<div style={styles.border}>
<Router>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/movies/vvf">V for Vendetta</Link>
</li>
</ul>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/movies/:movieId" element={<Movie />}>
<Route path=":charId" element={<Character />} />
</Route>
</Routes>
</Router>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
/*
Root route (home) - http://localhost:3000/
Movies route - http://localhost:3000/movies/vvf
Inside Movie, we have Characters link
http://localhost:3000/movies/vvf/v
http://localhost:3000/movies/vvf/evey
*/
How to tell React Router where inside the parent Route (Movie)
should it render the child Route (Character)
.
<Outlet></Outlet>
Nothing new here <Route path="/about" element={<About name="Avinash" />} />
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
const Home = () => <h1>Home</h1>;
const About = ({ name }) => <h1>About - {name}</h1>;
const App = () => (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About name="Avinash" />} />
</Routes>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
/*When we select /about route,
URL : http://localhost:3000/about
Component output : About - Avinash
*/
React Router has two different ways to programmatically navigate,
navigate
method.Navigate
component.useNavigate
imperative methodnavigate
method, we need to use React Router’s useNavigate
Hook.navigate
the new path (URL) we’d like the user to be taken to when navigate
is invoked.import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Routes,
Route,
useNavigate,
Link,
} from "react-router-dom";
const Dashboard = () => (
<div>
<h1>Dashboard</h1>
<Link to="/">go to form</Link>
</div>
);
const Register = () => {
const navigate = useNavigate();
const handleSubmit = () => {
navigate("/dashboard");
};
return (
<div>
<h1>Register</h1>
<form>
email: <input />
password: <input />
<input type="button" value="Submit" onClick={handleSubmit} />
</form>
</div>
);
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Register />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Router>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
<Navigate>
component declarative method<Navigate>
works just like any other React component, however, instead of rendering some UI, it navigates the user to a new location.
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
Link,
} from "react-router-dom";
const Dashboard = () => (
<div>
<h1>Dashboard</h1>
<Link to="/">go to form</Link>
</div>
);
const Register = () => {
const [toDashboard, setToDashboard] = React.useState(false);
const handleSubmit = () => {
setToDashboard(true);
};
if (toDashboard === true) {
return <Navigate to="/dashboard" />;
}
return (
<div>
<h1>Register</h1>
<form>
email: <input />
password: <input />
<input type="button" value="Submit" onClick={handleSubmit} />
</form>
</div>
);
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Register />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Router>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
The URI
https://www.amazon.com/Atomic-Habits-Proven-Build-Break/dp/0735211299/?_encoding=UTF8&pd_rd_w=BCpfg
Query Strings starts with ? and multiple strings are separated by &.
JavaScript/Browser has a built-in utility to work with query strings.
Click here for full documentation - URLSearchParams
In React Router,we have useSearchParams
Hook which is a small wrapper over browsers built-in URLSearchParams
which gives us utility methods for dealing with query strings.
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter,
Routes,
Route,
Link,
useSearchParams,
} from "react-router-dom";
const Home = () => (
<div>
<h1>Home</h1>
<Link to="/about?name=avinash&profession=engineer">go to about</Link>
</div>
);
const About = () => {
const [searchParams, _] = useSearchParams();
return (
<div>
<h1>About</h1>
<p>name : {searchParams.get("name")}</p>
<p>profession : {searchParams.get("profession")}</p>
</div>
);
};
const App = () => (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
/*
When we click "About" route, we see
URL - http://localhost:3000/about
UI -
About
name :
profession :
When we click on link from home to about, then
URL - http://localhost:3000/about?name=avinash&profession=engineer
UI -
About
name : avinash
profession : engineer
*/
All you have to do is render a Route with a path of *****, and React Router will make sure to only render the element if none of the other Routes match.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const NotFound = () => <h1>NOOOOOOT FOUND</h1>;
const App = () => (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<hr />
<Routes>
<Route path="*" element={<NotFound />} />
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
/*
URL - http://localhost:3000/
UI - Home
URL - http://localhost:3000/about
UI - About
URL - http://localhost:3000/settings
UI - NOOOOOOT FOUND
*/
Often we need this feature in building a Sidebar or Breadcrumbs.
How to render multiple components for matching single route?
In React Router, we can render another multiple sets of Routes
anywhere in our app like in the Sidebar, Breadcrumbs etc.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
const routes = [
{
path: "/",
Sidebar: () => <div>home!</div>,
Main: () => <h2>Home</h2>,
},
{
path: "/bubblegum",
Sidebar: () => <div>bubblegum!</div>,
Main: () => <h2>Bubblegum</h2>,
},
{
path: "/shoelaces",
Sidebar: () => <div>shoelaces!</div>,
Main: () => <h2>Shoelaces</h2>,
},
];
const App = () => {
return (
<Router>
<div style=>
<div
style=
>
<ul style=>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/bubblegum">Bubblegum</Link>
</li>
<li>
<Link to="/shoelaces">Shoelaces</Link>
</li>
</ul>
<Routes>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
element={<route.Sidebar />}
/>
))}
</Routes>
</div>
<div style=>
<Routes>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
exact={route.exact}
element={<route.Main />}
/>
))}
</Routes>
</div>
</div>
</Router>
);
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
active
and added the 👉 emoji to whatever Link was active.useLocation
to get the app’s current location.import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter,
Route,
Routes,
Link,
useLocation,
} from "react-router-dom";
const LinkEnhanced = ({ children, to }) => {
const location = useLocation();
const match = location.pathname === to;
return (
<span className={match ? "active" : ""}>
{match ? "👉 " : ""}
<Link to={to}>{children}</Link>
</span>
);
};
const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const Topics = () => <h1>Topics</h1>;
const App = () => (
<BrowserRouter>
<LinkEnhanced to="/">Home</LinkEnhanced>
<LinkEnhanced to="/about">About</LinkEnhanced>
<LinkEnhanced to="/topics">Topics</LinkEnhanced>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/topics" element={<Topics />} />
</Routes>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
What if, for every route we want to be private, instead of giving our Routes element prop the component we want it to render directly, we wrap it inside of a new component we’ll call RequireAuth
.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter,
Route,
Routes,
Link,
useLocation,
Navigate,
useNavigate,
} from "react-router-dom";
const Home = () => <h1>Home</h1>;
const Pricing = () => <h1>Pricing</h1>;
const Settings = () => <h1>Settings</h1>;
const Login = () => {
const location = useLocation();
const navigate = useNavigate();
const handleLogin = () => {
// Let's assume,
// Here is the API call the login a user.
// We execute next line after logged in
localStorage.setItem("auth", "YES");
navigate(location.state?.prevPath || "/settings");
};
return (
<div>
<h1>Login Page </h1>
<button onClick={handleLogin}>LOGIN</button>
{JSON.stringify(location)}
</div>
);
};
const RequireAuth = ({ children }) => {
const isLoggedIn = Boolean(localStorage.getItem("auth"));
const location = useLocation();
return isLoggedIn === true ? (
children
) : (
<Navigate to="/login" state= />
);
};
const App = () => (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/pricing">Pricing</Link>
<Link to="/settings">Settings</Link>
<Link to="/login">Login</Link>
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/pricing" element={<Pricing />} />
<Route
path="/settings"
element={
<RequireAuth>
<Settings />
</RequireAuth>
}
/>
<Route path="/login" element={<Login />} />
</Routes>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
RequireAuth
/login
).useLocation
hook, and pass that as a state prop when we redirect them to /login
, after they authenticated, we can redirect them back to this original path.localStorage
time to time to play with featuresrc/index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
const Home = React.lazy(() => import("./Home"));
const Products = React.lazy(() => import("./Products"));
const Services = React.lazy(() => import("./Services"));
const Administration = React.lazy(() => import("./Administration"));
const App = () => (
<BrowserRouter>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/products">Products</Link>
</li>
<li>
<Link to="/services">Services</Link>
</li>
<li>
<Link to="/administration">Administration</Link>
</li>
</ul>
<hr />
<React.Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/services" element={<Services />} />
<Route path="/administration" element={<Administration />} />
</Routes>
</React.Suspense>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
// src/Home.js
export default () => "Home";
// src/Products.js
export default () => "Products";
// src/Services.js
export default () => "Services";
// src/Administration.js
export default () => "Administration";
We can place the Suspense
component anywhere above the lazy component. We can even wrap multiple lazy components with a single Suspense component.
Don’t over use the Lazy Loading or Code-Splitting for everything, check the UX and DX before implementing.
create-react-app
(CRA) uses both Jest and React Testing Library by default.
Let’s create a CRA app, npx create-react-app react-testing
src/App.js - Default react App.js code
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
src/App.test.js Default react testing code for App.js
import { render, screen } from "@testing-library/react";
import App from "./App";
test("renders learn react link", () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
Run the app using npm run test
then we will see below output at terminal.
render
method to virtually render the App
component imported from App.js
file and append it to the document.body
node.screen
object.expect
and RTL’s toBeInTheDocument
to verify the correctness.How to see the how and what DOM/HTML generated by RTL ??
To see the result of the render()
call, we can use the screen.debug()
method:
src/App.test.js
import { render, screen } from "@testing-library/react";
import App from "./App";
test("renders learn react link", () => {
render(<App />);
screen.debug();
});
// Outputs in the terminal
{
/* <body>
<div>
<div class="App">
<header class="App-header">
<img alt="logo" class="App-logo" src="logo.svg" />
<p>
Edit
<code>src/App.js</code>
and save to reload.
</p>
<a
class="App-link"
href="https://reactjs.org"
rel="noopener noreferrer"
target="_blank"
>
Learn React
</a>
</header>
</div>
</div>
</body>; */
}
screen
object also has the DOM testing methods already bound into it.screen.getByText()
to query the anchor <a>
element by its textContent
value.expect
method from Jest and RTL’s toBeInTheDocument
to verify the correctness.RTL has several methods to find an element in DOM by specific attributes.
getByText()
find the element by its textContent
value
getByRole()
: by its role
attribute value
getByPlaceholderText()
: by its placeholder
attribute value
getByDisplayValue()
: by its value
attribute, usually for <input>
elements.
getByTestId()
method, which allows us to find an element by its data-testid
attribute.
import { render, screen } from "@testing-library/react";
test("<App>", () => {
render(
<>
<div role="article" data-testid="my-article">
Lipsum
</div>
Name : <input value="avinash" placeholder="enter name" />
</>
);
expect(screen.getByText("Lipsum").textContent).toEqual("Lipsum");
expect(screen.getByRole("article").textContent).toEqual("Lipsum");
expect(screen.getByPlaceholderText("enter name..").value).toEqual("avinash");
expect(screen.getByDisplayValue("avinash").placeholder).toEqual("enter name");
expect(screen.getByTestId("my-article")).toBeInTheDocument();
});
How to test DOM events, such as clicking on a button and typing values into a textbox etc.
The user-event
library is peer depended library for simulating user-browser interaction.
Let’s build an app,
src/App.js
import React from "react";
import "./App.css";
function App() {
const [count, setCount] = React.useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>+</button>
<span>{count}</span>
</>
);
}
export default App;
Next, we create a test that finds the button and simulates a click event by using the userEvent.click()
method. Once the button is clicked, we can assert the test is a success by inspecting whether count is incremented or not.
src/App.test.js
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import App from "./App";
test("<App>", () => {
render(<App />);
const button = screen.getByText("+");
userEvent.click(button);
expect(screen.getByText("1")).toBeInTheDocument();
screen.debug();
});
// outputs
{
/* <body>
<div>
<button>+</button>
<span>1</span>
</div>
</body>; */
}
The user-event
library also has several other methods like dblClick
for double clicking an element and type
for typing into a textbox and so on.
Note:
userEvent.click(button);
two times and notice the output.The simplest way to define a class component is to write a ES6 class
:
import React from "react";
import ReactDOM from "react-dom/client";
class Hello extends React.Component {
render() {
return <h1>Hello World</h1>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Hello />);
React.Component
render()
method returning react elements/JSX.<Hello />
import React from "react";
import ReactDOM from "react-dom/client";
class Hello extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Hello name={"Avinash"} />);
class
component, we can get access to props from the props key on the component’s instance this
.this.props
to access the props passed to the componentsroot.render()
with the <Hello name={"Avinash"} />
element.Hello
component with name={"Avinash"}
as the props.Hello
component returns a <h1>Hello, Avinash</h1>
element as the result.
React DOM efficiently updates the DOM to match <h1>Hello, Avinash</h1>
.import React from "react";
import ReactDOM from "react-dom/client";
class Hello extends React.Component {
state = {
name: "avinash",
};
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Hello />);
state
static member of ES6 class spec and isn’t a React specific method.state
to the component, we can now access it (via this.state
) anywhere in our class.setState
and it lives on the component’s instance, this
.import React from "react";
import ReactDOM from "react-dom/client";
class Hello extends React.Component {
state = {
name: "Avinash",
};
updateName = () => {
this.setState({
name: "React",
});
};
render() {
return (
<>
<h1>Hello, {this.state.name}</h1>
<button onClick={this.updateName}>Change Name</button>
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Hello />);
this.setState()
invoked, React will update the name
property on the component’s state to be whatever new name is.Every time a React app runs, all of our components go through a specific lifecycle, we can break down that lifecycle into three parts.
Here are the most common things occurs in any typical application (in order in which they occur).
Will see how these are fit into React components lifecycle methods.
import React from "react";
import ReactDOM from "react-dom/client";
class Hello extends React.Component {
state = {
// Set the component’s initial state
name: "Avinash",
place: "Mysore",
};
componentDidMount() {
// Make an Ajax requests/http calls/Network calls
fetch("https://jsonplaceholder.typicode.com/users/4")
.then((res) => res.json())
.then((user) => {
this.setState({ name: user.name });
});
}
render() {
// Render a DOM node (return React elements)
return (
<>
<h1>
Hello, {this.state.name}, {this.state.place}
</h1>
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Hello />);
state
static variablerender
.render
lifecycle method in order to describe (using JSX) the type of DOM node you want to render. (Actual DOM or HTML element will be created at ReactDOM.render()
.componentDidMount
is invoked only one time when the component is first mounted to the DOM. Because of this, it’s a great place to make an http calls.Note: Set up listeners, Timers
Similar to making an Ajax http calls, we can set up any listeners once the component has been mounted to the DOM, i.e., in
componentDidMount
. Why here, because component is already mounted and DOM APIs area available for access.Ex,
componentDidMount() { this.timerId = setTimeout(() => { console.log("timeout"); }, 1000); window.addEventListener("resize", () => {}); }
We want to hook into when a component updates its state or receives new data via props? For an example,
It’s important to note that render
will be invoked not only when the component is first added to the DOM, but also any time after that when its state
changes (via setState
) or when it receives new, updated props
.
componentDidUpdate
is invoked after the component’s local state changes or after it receives new props - but it’s not invoked on the initial render. It’s passed two arguments, the component’s previous props and the component’s previous state.
Ex, Prop changing,
import React from "react";
import ReactDOM from "react-dom/client";
class Hello extends React.Component {
state = {
name: "Avinash",
place: "Mysore",
};
componentDidMount() {
fetch(`https://jsonplaceholder.typicode.com/users/${this.props.id}`)
.then((res) => res.json())
.then((user) => {
this.setState({ name: user.name });
});
}
componentDidUpdate(prevProps, prevState) {
// Called on when, component re-renders (that is state change or props change)
if (prevProps.id != this.props.id)
fetch(`https://jsonplaceholder.typicode.com/users/${this.props.id}`)
.then((res) => res.json())
.then((user) => {
this.setState({ name: user.name });
});
}
render() {
return (
<>
<h1>
Hello, {this.state.name}, {this.state.place}
</h1>
</>
);
}
}
function UserSelector() {
const [selectedUser, setSelectedUser] = React.useState(1);
return (
<>
<button onClick={() => setSelectedUser(2)}>2</button>
<button onClick={() => setSelectedUser(3)}>3</button>
<Hello id={selectedUser}></Hello>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<UserSelector />);
Ex, Internal/local component state changing,
import React from "react";
import ReactDOM from "react-dom/client";
class Hello extends React.Component {
state = {
name: "Avinash",
place: "Mysore",
id: 1,
};
componentDidMount() {
fetch(`https://jsonplaceholder.typicode.com/users/${this.state.id}`)
.then((res) => res.json())
.then((user) => {
this.setState({ name: user.name });
});
}
componentDidUpdate(prevProps, prevState) {
// Called on when, component re-renders (that is state change or props change)
if (prevState.id != this.state.id)
fetch(`https://jsonplaceholder.typicode.com/users/${this.state.id}`)
.then((res) => res.json())
.then((user) => {
this.setState({ name: user.name });
});
}
render() {
return (
<>
<button onClick={() => this.setState({ id: 2 })}>2</button>
<button onClick={() => this.setState({ id: 3 })}>3</button>
<h1>
Hello, {this.state.name}, {this.state.place}
</h1>
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Hello></Hello>);
This allows you to compare the previous props/state to the current props/state so you can decide if you need to do anything.
Re-setting a Listener or timers
Similar to re-fetching data, we’d usecomponentDidUpdate
to listen for prop/state changes in order to re-set a listener.
What should happen when Components gets removed from DOM. Nullify timers, remove event listeners, otherwise memory leaks.
To do this, you can hook into React’s componentWillUnmount
lifecycle method. It’ll be called when the component is about to be removed from the DOM.
import React from "react";
import ReactDOM from "react-dom/client";
class Home extends React.Component {
componentDidMount() {
this.timerId = setTimeout(() => {
console.log("timeout");
}, 1000);
window.addEventListener("resize", () => {});
}
componentWillUnmount() {
// clearTimeout(this.timerId);
window.removeEventListener("resize", () => {});
}
render() {
return <></>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Home></Home>);
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
constructor(props) {
// Good for establishing the initial state of a component
super(props);
this.state = {};
}
componentDidMount() {
// Invoked once the component is mounted to the DOM.
// Good for making AJAX requests.
}
componentDidUpdate() {
// Invoked immediately after updating occurs.
// Good for AJAX requests based on changing props or DOM ope rations.
}
componentWillUnmount() {
// Called right before a component is unmounted.
// Good for cleaning up listeners.
}
render() {
return <></>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Home></Home>);
Other less or rarely used lifecycle methods.
This code will produces the blank white scree in the browser giving no clue to user, that what happened?
import React from "react";
import ReactDOM from "react-dom/client";
function Welcome({ name }) {
return <>Welcome, {name.toUpperCase()}</>;
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Welcome />);
If we open the browser’s console, we’ll see something like index.js:5 Uncaught TypeError: Cannot read properties of undefined (reading 'toUpperCase')
in red color.
Let’s handle the error using Try catch.
import React from "react";
import ReactDOM from "react-dom/client";
function ErrorComponent({ errorMessage }) {
return <h1 style=>{errorMessage}</h1>;
}
function Welcome({ name }) {
try {
return <div>Welcome, {name.toUpperCase()}</div>;
} catch (error) {
return <ErrorComponent errorMessage={error.message} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<Welcome />);
This will show the error message in browser’s UI - Cannot read properties of undefined (reading 'toUpperCase')
.
But, but what if I don’t want to wrap every component in my app in a try/catch block?
static getDerivedStateFromError(error)
This lifecycle is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.
import React from "react";
import ReactDOM from "react-dom/client";
function ErrorComponent({ message }) {
return <h1>{message}</h1>;
}
class ErrorBoundary extends React.Component {
state = { errorMessage: "" };
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { errorMessage: error.message };
}
render() {
if (this.state.errorMessage) {
// You can render any custom fallback UI
return <ErrorComponent message={this.state.errorMessage} />;
}
return this.props.children;
}
}
function Welcome({ name }) {
return <div>Welcome, {name.toUpperCase()}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(
<ErrorBoundary>
<Welcome />
</ErrorBoundary>
);
Note:
getDerivedStateFromError()
is called during therender
phase, so side-effects are not permitted. For those use cases, usecomponentDidCatch()
instead.
componentDidCatch(error, info)
Produce the condition where getDerivedStateFromError is insufficient and fix that issue with componentDidCatch with an example.
Redux is a predictable state container for JavaScript apps.
Redux can be described in three fundamental principles:
The global state of your application is stored in an object tree within a single store.
console.log(store.getState())
/* Prints
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
*/
The only way to change the state is to emit an action, an object describing what happened.
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
})
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
})
To specify how the state tree is transformed by actions, you write pure reducers.
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
import { combineReducers, createStore } from 'redux'
const reducer = combineReducers({ visibilityFilter, todos })
const store = createStore(reducer)
npx create-react-app redux-counter
cd redux-counter
npm install redux react-redux
index.js
filesrc/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { createStore } from "redux";
import { Provider, useDispatch, useSelector } from "react-redux";
function counterReducer(state = 0, action) {
if (action.type == "up") {
return state + action.value;
}
if (action.type == "down") {
return state - action.value;
}
if (action.type == "reset") {
return 0;
}
return state;
}
const store = createStore(counterReducer);
function Counter() {
const dispatch = useDispatch();
const count = useSelector(function (state) {
return state;
});
return (
<>
<button onClick={() => dispatch({ type: "up", value: 1 })}>+</button>
<span>{count}</span>
<button onClick={() => dispatch({ type: "down", value: 1 })}>-</button>
<button onClick={() => dispatch({ type: "reset" })}>reset</button>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(
<Provider store={store}>
<Counter />
</Provider>
);
——————->