mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-17 04:11:42 +08:00
Compare commits
1 Commits
chore/java
...
chore/upgr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e0ce7246a |
@@ -30,12 +30,11 @@ Find [the content directory inside the relevant roadmap](https://github.com/kamr
|
||||
|
||||
## Guidelines
|
||||
|
||||
- <p><strong>Adding everything available out there is not the goal!</strong><br />
|
||||
- <p><strong>Adding everything available out there is not the goal!</strong><br />
|
||||
The roadmaps represent the skillset most valuable today, i.e., if you were to enter any of the listed fields today, what would you learn?! There might be things that are of-course being used today but prioritize the things that are most in demand today, e.g., agreed that lots of people are using angular.js today but you wouldn't want to learn that instead of React, Angular, or Vue. Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included.</p>
|
||||
- <p><strong>Do not add things you have not evaluated personally!</strong><br />
|
||||
- <p><strong>Do not add things you have not evaluated personally!</strong><br />
|
||||
Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included. Have you read this book? Can you give a short article?</p>
|
||||
- <p><strong>Create a Single PR for Content Additions</strong></p>
|
||||
|
||||
If you are planning to contribute by adding content to the roadmaps, I recommend you to clone the repository, add content to the [content directory of the roadmap](./src/data/roadmaps/) and create a single PR to make it easier for me to review and merge the PR.
|
||||
If you are planning to contribute by adding content to the roadmaps, I recommend you to clone the repository, add content to the [content directory of the roadmap](./content/roadmaps/) and create a single PR to make it easier for me to review and merge the PR.
|
||||
- Write meaningful commit messages
|
||||
- Look at the existing issues/pull requests before opening new ones
|
||||
|
||||
@@ -30,27 +30,22 @@
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"astro": "^3.0.5",
|
||||
"astro-compress": "^2.0.8",
|
||||
"dracula-prism": "^2.1.13",
|
||||
"jose": "^4.14.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"nanostores": "^0.9.2",
|
||||
"node-html-parser": "^6.1.5",
|
||||
"npm-check-updates": "^16.10.12",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.0.0",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"rehype-external-links": "^2.1.0",
|
||||
"roadmap-renderer": "^1.0.6",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwindcss": "^3.3.3"
|
||||
"tailwindcss": "^3.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.35.1",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"csv-parser": "^3.0.0",
|
||||
"gh-pages": "^5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
|
||||
96
pnpm-lock.yaml
generated
96
pnpm-lock.yaml
generated
@@ -13,7 +13,7 @@ dependencies:
|
||||
version: 1.3.3
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(astro@3.0.5)(tailwindcss@3.3.3)
|
||||
version: 5.0.0(astro@3.0.5)(tailwindcss@3.3.2)
|
||||
'@fingerprintjs/fingerprintjs':
|
||||
specifier: ^3.4.1
|
||||
version: 3.4.1
|
||||
@@ -32,18 +32,12 @@ dependencies:
|
||||
astro-compress:
|
||||
specifier: ^2.0.8
|
||||
version: 2.0.8
|
||||
dracula-prism:
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13
|
||||
jose:
|
||||
specifier: ^4.14.4
|
||||
version: 4.14.4
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
lucide-react:
|
||||
specifier: ^0.274.0
|
||||
version: 0.274.0(react@18.0.0)
|
||||
nanostores:
|
||||
specifier: ^0.9.2
|
||||
version: 0.9.2
|
||||
@@ -53,15 +47,9 @@ dependencies:
|
||||
npm-check-updates:
|
||||
specifier: ^16.10.12
|
||||
version: 16.10.12
|
||||
prismjs:
|
||||
specifier: ^1.29.0
|
||||
version: 1.29.0
|
||||
react:
|
||||
specifier: ^18.0.0
|
||||
version: 18.0.0
|
||||
react-confetti:
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(react@18.0.0)
|
||||
react-dom:
|
||||
specifier: ^18.0.0
|
||||
version: 18.0.0(react@18.0.0)
|
||||
@@ -75,8 +63,8 @@ dependencies:
|
||||
specifier: ^1.6.6
|
||||
version: 1.6.6
|
||||
tailwindcss:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
specifier: ^3.0.23
|
||||
version: 3.3.2
|
||||
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
@@ -84,13 +72,10 @@ devDependencies:
|
||||
version: 1.35.1
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.9
|
||||
version: 0.5.9(tailwindcss@3.3.3)
|
||||
version: 0.5.9(tailwindcss@3.3.2)
|
||||
'@types/js-cookie':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
'@types/prismjs':
|
||||
specifier: ^1.26.0
|
||||
version: 1.26.0
|
||||
csv-parser:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -157,7 +142,7 @@ packages:
|
||||
remark-parse: 10.0.2
|
||||
remark-rehype: 10.1.0
|
||||
remark-smartypants: 2.0.0
|
||||
shiki: 0.14.4
|
||||
shiki: 0.14.3
|
||||
unified: 10.1.2
|
||||
unist-util-visit: 4.1.2
|
||||
vfile: 5.3.7
|
||||
@@ -200,7 +185,7 @@ packages:
|
||||
zod: 3.22.2
|
||||
dev: false
|
||||
|
||||
/@astrojs/tailwind@5.0.0(astro@3.0.5)(tailwindcss@3.3.3):
|
||||
/@astrojs/tailwind@5.0.0(astro@3.0.5)(tailwindcss@3.3.2):
|
||||
resolution: {integrity: sha512-bMZZNNm/SW+ijUKMQDhdiuNWDdR3CubEKUHb2Ran4Arx1ikWn/kKIkFDXUV+MUnsLa7s19x9VMRlARRyKbqMkQ==}
|
||||
peerDependencies:
|
||||
astro: ^3.0.0
|
||||
@@ -210,7 +195,7 @@ packages:
|
||||
autoprefixer: 10.4.15(postcss@8.4.29)
|
||||
postcss: 8.4.29
|
||||
postcss-load-config: 4.0.1(postcss@8.4.29)
|
||||
tailwindcss: 3.3.3
|
||||
tailwindcss: 3.3.2
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: false
|
||||
@@ -1101,7 +1086,7 @@ packages:
|
||||
defer-to-connect: 2.0.1
|
||||
dev: false
|
||||
|
||||
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.3):
|
||||
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.2):
|
||||
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders'
|
||||
@@ -1110,7 +1095,7 @@ packages:
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.merge: 4.6.2
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 3.3.3
|
||||
tailwindcss: 3.3.2
|
||||
dev: true
|
||||
|
||||
/@tootallnate/once@2.0.0:
|
||||
@@ -1239,10 +1224,6 @@ packages:
|
||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||
dev: false
|
||||
|
||||
/@types/prismjs@1.26.0:
|
||||
resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==}
|
||||
dev: true
|
||||
|
||||
/@types/prop-types@15.7.5:
|
||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
||||
dev: false
|
||||
@@ -1484,7 +1465,7 @@ packages:
|
||||
semver: 7.5.4
|
||||
server-destroy: 1.0.1
|
||||
sharp: 0.32.5
|
||||
shiki: 0.14.4
|
||||
shiki: 0.14.3
|
||||
string-width: 6.1.0
|
||||
strip-ansi: 7.1.0
|
||||
tsconfig-resolver: 3.0.1
|
||||
@@ -1493,7 +1474,7 @@ packages:
|
||||
vfile: 5.3.7
|
||||
vite: 4.4.9
|
||||
vitefu: 0.2.4(vite@4.4.9)
|
||||
which-pm: 2.1.1
|
||||
which-pm: 2.0.0
|
||||
yargs-parser: 21.1.1
|
||||
zod: 3.21.1
|
||||
transitivePeerDependencies:
|
||||
@@ -1524,7 +1505,7 @@ packages:
|
||||
dependencies:
|
||||
browserslist: 4.21.10
|
||||
caniuse-lite: 1.0.30001525
|
||||
fraction.js: 4.3.6
|
||||
fraction.js: 4.3.4
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
postcss: 8.4.29
|
||||
@@ -1616,7 +1597,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001525
|
||||
electron-to-chromium: 1.4.508
|
||||
electron-to-chromium: 1.4.506
|
||||
node-releases: 2.0.13
|
||||
update-browserslist-db: 1.0.11(browserslist@4.21.10)
|
||||
dev: false
|
||||
@@ -2113,10 +2094,6 @@ packages:
|
||||
is-obj: 2.0.0
|
||||
dev: false
|
||||
|
||||
/dracula-prism@2.1.13:
|
||||
resolution: {integrity: sha512-mgm8Nr/X0RGUz/wpha88dKN/xw0QIGK8BQmWXrzgtOp9be+qcKiFEa2J5SQ3+/WNvL5sPPtNQXPL+Vy//Q8+dg==}
|
||||
dev: false
|
||||
|
||||
/dset@3.1.2:
|
||||
resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2126,8 +2103,8 @@ packages:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.508:
|
||||
resolution: {integrity: sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==}
|
||||
/electron-to-chromium@1.4.506:
|
||||
resolution: {integrity: sha512-xxGct4GPAKSRlrLBtJxJFYy74W11zX6PO9GyHgl/U+2s3Dp0ZEwAklDfNHXOWcvH7zWMpsmgbR0ggEuaYAVvHA==}
|
||||
dev: false
|
||||
|
||||
/email-addresses@5.0.0:
|
||||
@@ -2428,8 +2405,8 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/fraction.js@4.3.6:
|
||||
resolution: {integrity: sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==}
|
||||
/fraction.js@4.3.4:
|
||||
resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==}
|
||||
dev: false
|
||||
|
||||
/fs-constants@1.0.0:
|
||||
@@ -3258,14 +3235,6 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/lucide-react@0.274.0(react@18.0.0):
|
||||
resolution: {integrity: sha512-qiWcojRXEwDiSimMX1+arnxha+ROJzZjJaVvCC0rsG6a9pUPjZePXSq7em4ZKMp0NDm1hyzPNkM7UaWC3LU2AA==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.0.0
|
||||
dev: false
|
||||
|
||||
/magic-string@0.30.3:
|
||||
resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -4638,16 +4607,6 @@ packages:
|
||||
strip-json-comments: 2.0.1
|
||||
dev: false
|
||||
|
||||
/react-confetti@6.1.0(react@18.0.0):
|
||||
resolution: {integrity: sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==}
|
||||
engines: {node: '>=10.18'}
|
||||
peerDependencies:
|
||||
react: ^16.3.0 || ^17.0.1 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.0.0
|
||||
tween-functions: 1.2.0
|
||||
dev: false
|
||||
|
||||
/react-dom@18.0.0(react@18.0.0):
|
||||
resolution: {integrity: sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw==}
|
||||
peerDependencies:
|
||||
@@ -5042,8 +5001,8 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/shiki@0.14.4:
|
||||
resolution: {integrity: sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==}
|
||||
/shiki@0.14.3:
|
||||
resolution: {integrity: sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==}
|
||||
dependencies:
|
||||
ansi-sequence-parser: 1.1.1
|
||||
jsonc-parser: 3.2.0
|
||||
@@ -5354,8 +5313,8 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
dev: false
|
||||
|
||||
/tailwindcss@3.3.3:
|
||||
resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==}
|
||||
/tailwindcss@3.3.2:
|
||||
resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -5379,6 +5338,7 @@ packages:
|
||||
postcss-load-config: 4.0.1(postcss@8.4.29)
|
||||
postcss-nested: 6.0.1(postcss@8.4.29)
|
||||
postcss-selector-parser: 6.0.13
|
||||
postcss-value-parser: 4.2.0
|
||||
resolve: 1.22.4
|
||||
sucrase: 3.34.0
|
||||
transitivePeerDependencies:
|
||||
@@ -5515,10 +5475,6 @@ packages:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/tween-functions@1.2.0:
|
||||
resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==}
|
||||
dev: false
|
||||
|
||||
/type-fest@0.13.1:
|
||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -5805,14 +5761,6 @@ packages:
|
||||
path-exists: 4.0.0
|
||||
dev: false
|
||||
|
||||
/which-pm@2.1.1:
|
||||
resolution: {integrity: sha512-xzzxNw2wMaoCWXiGE8IJ9wuPMU+EYhFksjHxrRT8kMT5SnocBPRg69YAMtyV4D12fP582RA+k3P8H9J5EMdIxQ==}
|
||||
engines: {node: '>=8.15'}
|
||||
dependencies:
|
||||
load-yaml-file: 0.2.0
|
||||
path-exists: 4.0.0
|
||||
dev: false
|
||||
|
||||
/which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
@@ -37,7 +37,6 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [Computer Science Roadmap](https://roadmap.sh/computer-science)
|
||||
- [AI and Data Scientist Roadmap](https://roadmap.sh/ai-data-scientist)
|
||||
- [QA Roadmap](https://roadmap.sh/qa)
|
||||
- [Python Roadmap](https://roadmap.sh/python)
|
||||
- [Software Architect Roadmap](https://roadmap.sh/software-architect)
|
||||
- [Software Design and Architecture Roadmap](https://roadmap.sh/software-design-architecture)
|
||||
- [JavaScript Roadmap](https://roadmap.sh/javascript)
|
||||
@@ -51,6 +50,7 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [GraphQL Roadmap](https://roadmap.sh/graphql)
|
||||
- [Android Roadmap](https://roadmap.sh/android)
|
||||
- [Flutter Roadmap](https://roadmap.sh/flutter)
|
||||
- [Python Roadmap](https://roadmap.sh/python)
|
||||
- [Go Roadmap](https://roadmap.sh/golang)
|
||||
- [Java Roadmap](https://roadmap.sh/java)
|
||||
- [Spring Boot Roadmap](https://roadmap.sh/spring-boot)
|
||||
|
||||
@@ -9,9 +9,9 @@ import { LinkedInButton } from './LinkedInButton';
|
||||
|
||||
<Popup id='login-popup' title='' subtitle=''>
|
||||
<div class='text-center'>
|
||||
<p class='mb-3 text-2xl font-semibold leading-5 text-slate-900'>
|
||||
<h2 class='mb-3 text-2xl font-semibold leading-5 text-slate-900'>
|
||||
Login to your account
|
||||
</p>
|
||||
</h2>
|
||||
<p class='mt-2 text-sm leading-4 text-slate-600'>
|
||||
You must be logged in to perform this action.
|
||||
</p>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import BestPracticesIcon from '../../icons/best-practices.svg';
|
||||
import ClipboardIcon from '../../icons/clipboard.svg';
|
||||
import GuideIcon from '../../icons/guide.svg';
|
||||
import HomeIcon from '../../icons/home.svg';
|
||||
import RoadmapIcon from '../../icons/roadmap.svg';
|
||||
@@ -54,13 +53,6 @@ const defaultPages: PageType[] = [
|
||||
group: 'Pages',
|
||||
icon: BestPracticesIcon.src,
|
||||
},
|
||||
{
|
||||
id: 'questions',
|
||||
url: '/questions',
|
||||
title: 'Questions',
|
||||
group: 'Pages',
|
||||
icon: ClipboardIcon.src,
|
||||
},
|
||||
{
|
||||
id: 'guides',
|
||||
url: '/guides',
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactConfetti from 'react-confetti';
|
||||
|
||||
type ConfettiPosition = {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
};
|
||||
|
||||
type ConfettiProps = {
|
||||
pieces?: number;
|
||||
element?: HTMLElement | null;
|
||||
onDone?: () => void;
|
||||
};
|
||||
|
||||
export function Confetti(props: ConfettiProps) {
|
||||
const { element = document.body, onDone = () => null, pieces = 40 } = props;
|
||||
|
||||
const [confettiPos, setConfettiPos] = useState<
|
||||
undefined | ConfettiPosition
|
||||
>();
|
||||
|
||||
function populateConfettiPosition(element: HTMLElement) {
|
||||
const elRect = element.getBoundingClientRect();
|
||||
|
||||
// set confetti position, keeping in mind the scroll values
|
||||
setConfettiPos({
|
||||
x: elRect?.x || 0,
|
||||
y: (elRect?.y || 0) + window.scrollY,
|
||||
w: elRect?.width || 0,
|
||||
h: elRect?.height || 0,
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!element) {
|
||||
setConfettiPos(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
populateConfettiPosition(element);
|
||||
}, [element]);
|
||||
|
||||
if (!confettiPos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactConfetti
|
||||
height={document.body.scrollHeight}
|
||||
numberOfPieces={pieces}
|
||||
recycle={false}
|
||||
onConfettiComplete={(confettiInstance) => {
|
||||
setConfettiPos(undefined);
|
||||
onDone();
|
||||
}}
|
||||
initialVelocityX={4}
|
||||
initialVelocityY={8}
|
||||
tweenDuration={10}
|
||||
confettiSource={{
|
||||
x: confettiPos.x,
|
||||
y: confettiPos.y,
|
||||
w: confettiPos.w,
|
||||
h: confettiPos.h,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export function NextButton(props: NextButtonProps) {
|
||||
|
||||
return (
|
||||
<button
|
||||
type={type as any}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={isLoading}
|
||||
className={
|
||||
|
||||
@@ -9,13 +9,13 @@ export const validTeamTypes = [
|
||||
{
|
||||
value: 'company',
|
||||
label: 'Company',
|
||||
icon: BuildingIcon.src,
|
||||
icon: BuildingIcon,
|
||||
description: 'Track the skills and learning progress of the tech team at your company',
|
||||
},
|
||||
{
|
||||
value: 'study_group',
|
||||
label: 'Study Group',
|
||||
icon: UsersIcon.src,
|
||||
icon: UsersIcon,
|
||||
description: 'Invite your friends or course-mates and track your learning progress together',
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -6,18 +6,11 @@ export interface FeaturedItemType {
|
||||
isNew?: boolean;
|
||||
url: string;
|
||||
text: string;
|
||||
allowBookmark?: boolean;
|
||||
}
|
||||
|
||||
export interface Props extends FeaturedItemType {}
|
||||
|
||||
const {
|
||||
isUpcoming = false,
|
||||
isNew = false,
|
||||
text,
|
||||
url,
|
||||
allowBookmark = true,
|
||||
} = Astro.props;
|
||||
const { isUpcoming = false, isNew = false, text, url } = Astro.props;
|
||||
---
|
||||
|
||||
<a
|
||||
@@ -33,17 +26,11 @@ const {
|
||||
{text}
|
||||
</span>
|
||||
|
||||
{
|
||||
allowBookmark && (
|
||||
<MarkFavorite
|
||||
resourceId={url.split('/').pop()!}
|
||||
resourceType={
|
||||
url.includes('best-practices') ? 'best-practice' : 'roadmap'
|
||||
}
|
||||
client:only='react'
|
||||
/>
|
||||
)
|
||||
}
|
||||
<MarkFavorite
|
||||
resourceId={url.split('/').pop()!}
|
||||
resourceType={url.includes('best-practices') ? 'best-practice' : 'roadmap'}
|
||||
client:only="react"
|
||||
/>
|
||||
|
||||
{
|
||||
isNew && (
|
||||
|
||||
@@ -4,16 +4,15 @@ import FeaturedItem, { FeaturedItemType } from './FeaturedItem.astro';
|
||||
export interface Props {
|
||||
featuredItems: FeaturedItemType[];
|
||||
heading: string;
|
||||
allowBookmark?: boolean;
|
||||
}
|
||||
|
||||
const { featuredItems, heading, allowBookmark = true } = Astro.props;
|
||||
const { featuredItems, heading } = Astro.props;
|
||||
---
|
||||
|
||||
<div class='relative border-b border-b-[#1e293c] py-10 sm:py-14'>
|
||||
<div class='container'>
|
||||
<h2
|
||||
class='text-md font-regular absolute -top-[17px] flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2'
|
||||
class='text-md font-regular absolute flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 -top-[17px] sm:left-1/2 sm:-translate-x-1/2'
|
||||
>
|
||||
{heading}
|
||||
</h2>
|
||||
@@ -23,7 +22,6 @@ const { featuredItems, heading, allowBookmark = true } = Astro.props;
|
||||
featuredItems.map((featuredItem) => (
|
||||
<li>
|
||||
<FeaturedItem
|
||||
allowBookmark={allowBookmark}
|
||||
text={featuredItem.text}
|
||||
url={featuredItem.url}
|
||||
isNew={featuredItem.isNew}
|
||||
|
||||
@@ -96,7 +96,6 @@ export function MarkFavorite({
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-label={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
|
||||
onClick={toggleFavoriteHandler}
|
||||
tabIndex={-1}
|
||||
className={`${isFavorite ? '' : 'opacity-30 hover:opacity-100'} ${
|
||||
|
||||
@@ -31,7 +31,8 @@ import Icon from './AstroIcon.astro';
|
||||
<a
|
||||
class='px-2 py-1.5 transition-colors hover:text-white sm:border-b-0 sm:px-0 sm:py-0'
|
||||
href='https://youtube.com/theroadmap?sub_confirmation=1'
|
||||
target='_blank'>YouTube</a>
|
||||
target='_blank'>YouTube</a
|
||||
>
|
||||
</p>
|
||||
|
||||
<div class='flex flex-col justify-between gap-12 sm:flex-row'>
|
||||
@@ -67,7 +68,6 @@ import Icon from './AstroIcon.astro';
|
||||
<a href='/privacy' class='hover:text-white'>Privacy</a>
|
||||
<span class='mx-1.5'>·</span>
|
||||
<a
|
||||
aria-label="Subscribe to YouTube channel"
|
||||
href='https://youtube.com/theroadmap?sub_confirmation=1'
|
||||
target='_blank'
|
||||
class='hover:text-white'
|
||||
@@ -75,7 +75,6 @@ import Icon from './AstroIcon.astro';
|
||||
<AstroIcon icon='youtube' class='inline-block h-5 w-5' />
|
||||
</a>
|
||||
<a
|
||||
aria-label="Follow on Twitter"
|
||||
href='https://twitter.com/roadmapsh'
|
||||
target='_blank'
|
||||
class='ml-2 hover:text-white'
|
||||
|
||||
@@ -16,22 +16,15 @@ const { url, title, description, isNew } = Astro.props;
|
||||
class='relative flex h-full flex-col rounded-md border border-gray-200 bg-white p-2.5 hover:border-gray-400 sm:rounded-lg sm:p-5'
|
||||
>
|
||||
<span
|
||||
class='text-md mb-0 font-semibold text-gray-900 hover:text-black sm:mb-1.5 sm:text-xl'
|
||||
class='font-semibold text-md mb-0 text-gray-900 hover:text-black sm:mb-1.5 sm:text-xl'
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
<span
|
||||
class='hidden text-sm leading-normal text-gray-400 sm:block'
|
||||
set:html={description}
|
||||
/>
|
||||
<span class='hidden text-sm leading-normal text-gray-400 sm:block' set:html={description} />
|
||||
|
||||
{
|
||||
isNew && (
|
||||
<span class='flex items-center gap-1.5 absolute bottom-1.5 right-1 rounded-sm text-xs font-semibold uppercase text-purple-500 sm:px-1.5'>
|
||||
<span class='relative flex h-2 w-2'>
|
||||
<span class='absolute inline-flex h-full w-full animate-ping rounded-full bg-purple-400 opacity-75' />
|
||||
<span class='relative inline-flex h-2 w-2 rounded-full bg-purple-500' />
|
||||
</span>
|
||||
<span class='absolute bottom-1 right-1 rounded-sm bg-yellow-300 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900 sm:px-1.5'>
|
||||
New
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { EmptyProgress } from './EmptyProgress';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { HeroRoadmaps } from './HeroRoadmaps';
|
||||
import { ProgressList } from './ProgressList';
|
||||
import {isLoggedIn} from "../../lib/jwt";
|
||||
|
||||
export type UserProgressResponse = {
|
||||
@@ -122,7 +122,7 @@ export function FavoriteRoadmaps() {
|
||||
<div className="container min-h-full">
|
||||
{!isLoading && progress.length == 0 && <EmptyProgress />}
|
||||
{progress.length > 0 && (
|
||||
<HeroRoadmaps customRoadmaps={[]} progress={progress} isLoading={isLoading} />
|
||||
<ProgressList progress={progress} isLoading={isLoading} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import type { UserProgressResponse } from './FavoriteRoadmaps';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon';
|
||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
import type { ResourceType } from '../../lib/resource-progress';
|
||||
import { MapIcon } from 'lucide-react';
|
||||
|
||||
type ProgressRoadmapProps = {
|
||||
url: string;
|
||||
percentageDone: number;
|
||||
allowFavorite?: boolean;
|
||||
|
||||
resourceId: string;
|
||||
resourceType: ResourceType;
|
||||
resourceTitle: string;
|
||||
isFavorite?: boolean;
|
||||
};
|
||||
function HeroRoadmap(props: ProgressRoadmapProps) {
|
||||
const {
|
||||
url,
|
||||
percentageDone,
|
||||
resourceType,
|
||||
resourceId,
|
||||
resourceTitle,
|
||||
isFavorite,
|
||||
allowFavorite = true,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
className="relative flex flex-col overflow-hidden rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300"
|
||||
>
|
||||
<span className="relative z-20">{resourceTitle}</span>
|
||||
|
||||
<span
|
||||
className="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]"
|
||||
style={{ width: `${percentageDone}%` }}
|
||||
></span>
|
||||
|
||||
{allowFavorite && (
|
||||
<MarkFavorite
|
||||
resourceId={resourceId}
|
||||
resourceType={resourceType}
|
||||
favorite={isFavorite}
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
type ProgressTitleProps = {
|
||||
icon: any;
|
||||
isLoading?: boolean;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export function HeroTitle(props: ProgressTitleProps) {
|
||||
const { isLoading = false, title, icon } = props;
|
||||
|
||||
return (
|
||||
<p className="mb-4 flex items-center text-sm text-gray-400">
|
||||
{!isLoading && icon}
|
||||
{isLoading && (
|
||||
<span className="mr-1.5">
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
{title}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
type ProgressListProps = {
|
||||
progress: UserProgressResponse;
|
||||
showCustomRoadmaps?: boolean;
|
||||
customRoadmaps: any[]; // @fixme implement this
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
export function HeroRoadmaps(props: ProgressListProps) {
|
||||
const {
|
||||
progress,
|
||||
isLoading = false,
|
||||
customRoadmaps = [{} /* @fixme implement this */],
|
||||
showCustomRoadmaps = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="relative pb-12 pt-4 sm:pt-7">
|
||||
{
|
||||
<HeroTitle
|
||||
icon={
|
||||
(<CheckIcon additionalClasses="mr-1.5 h-[14px] w-[14px]" />) as any
|
||||
}
|
||||
isLoading={isLoading}
|
||||
title="Your progress and favorite roadmaps."
|
||||
/>
|
||||
}
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{progress.map((resource) => (
|
||||
<HeroRoadmap
|
||||
key={resource.resourceId}
|
||||
resourceId={resource.resourceId}
|
||||
resourceType={resource.resourceType}
|
||||
resourceTitle={resource.resourceTitle}
|
||||
isFavorite={resource.isFavorite}
|
||||
percentageDone={
|
||||
((resource.skipped + resource.done) / resource.total) * 100
|
||||
}
|
||||
url={
|
||||
resource.resourceType === 'roadmap'
|
||||
? `/${resource.resourceId}`
|
||||
: `/best-practices/${resource.resourceId}`
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{showCustomRoadmaps && (
|
||||
<div className="mt-5">
|
||||
{
|
||||
<HeroTitle
|
||||
icon={<MapIcon className="mr-1.5 h-[14px] w-[14px]" />}
|
||||
title="Your custom roadmaps"
|
||||
/>
|
||||
}
|
||||
|
||||
{customRoadmaps.length === 0 && (
|
||||
<p className="rounded-md border border-dashed border-gray-800 p-2 text-sm text-gray-600">
|
||||
You haven't created any custom roadmaps yet.{' '}
|
||||
<button className="text-gray-500 underline underline-offset-2 hover:text-gray-400">
|
||||
Create one!
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{customRoadmaps.map((customRoadmap) => (
|
||||
<HeroRoadmap
|
||||
resourceId={'343434'}
|
||||
resourceType={'roadmap'}
|
||||
resourceTitle={'Frontend Roadmap Revised'}
|
||||
percentageDone={50}
|
||||
url={`/r?${'34343434'}`}
|
||||
allowFavorite={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
61
src/components/HeroSection/ProgressList.tsx
Normal file
61
src/components/HeroSection/ProgressList.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { UserProgressResponse } from './FavoriteRoadmaps';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon';
|
||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
|
||||
type ProgressListProps = {
|
||||
progress: UserProgressResponse;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
export function ProgressList(props: ProgressListProps) {
|
||||
const { progress, isLoading = false } = props;
|
||||
|
||||
return (
|
||||
<div className="relative pb-12 pt-4 sm:pt-7">
|
||||
<p className="mb-4 flex items-center text-sm text-gray-400">
|
||||
{!isLoading && (
|
||||
<CheckIcon additionalClasses={'mr-1.5 w-[14px] h-[14px]'} />
|
||||
)}
|
||||
{isLoading && (
|
||||
<span className="mr-1.5">
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
Your progress and favorite roadmaps.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{progress.map((resource) => {
|
||||
const url =
|
||||
resource.resourceType === 'roadmap'
|
||||
? `/${resource.resourceId}`
|
||||
: `/best-practices/${resource.resourceId}`;
|
||||
|
||||
const percentageDone =
|
||||
((resource.skipped + resource.done) / resource.total) * 100;
|
||||
|
||||
return (
|
||||
<a
|
||||
key={resource.resourceId}
|
||||
href={url}
|
||||
className="relative flex flex-col overflow-hidden rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300"
|
||||
>
|
||||
<span className="relative z-20">{resource.resourceTitle}</span>
|
||||
|
||||
<span
|
||||
className="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]"
|
||||
style={{ width: `${percentageDone}%` }}
|
||||
></span>
|
||||
<MarkFavorite
|
||||
resourceId={resource.resourceId}
|
||||
resourceType={resource.resourceType}
|
||||
favorite={resource.isFavorite}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -24,10 +24,10 @@ import AccountDropdown from './AccountDropdown.astro';
|
||||
>
|
||||
</li>
|
||||
<li class='hidden lg:inline'>
|
||||
<a href='/questions' class='text-gray-400 hover:text-white'>Questions</a>
|
||||
<a href='/guides' class='text-gray-400 hover:text-white'>Guides</a>
|
||||
</li>
|
||||
<li class='hidden lg:inline'>
|
||||
<a href='/guides' class='text-gray-400 hover:text-white'>Guides</a>
|
||||
<a href='/videos' class='text-gray-400 hover:text-white'>Videos</a>
|
||||
</li>
|
||||
<li>
|
||||
<kbd
|
||||
|
||||
@@ -19,8 +19,6 @@ function bindEvents() {
|
||||
...target.closest('button')?.dataset,
|
||||
};
|
||||
|
||||
const accountDropdown = document.querySelector('[data-account-dropdown]');
|
||||
|
||||
// If the user clicks on the logout button, remove the token cookie
|
||||
if (dataset.logoutButton !== undefined) {
|
||||
e.preventDefault();
|
||||
@@ -29,12 +27,6 @@ function bindEvents() {
|
||||
document.querySelector('[data-mobile-nav]')?.classList.remove('hidden');
|
||||
} else if (dataset.closeMobileNav !== undefined) {
|
||||
document.querySelector('[data-mobile-nav]')?.classList.add('hidden');
|
||||
} else if (
|
||||
accountDropdown &&
|
||||
!target?.closest('[data-account-dropdown]') &&
|
||||
!accountDropdown.classList.contains('hidden')
|
||||
) {
|
||||
accountDropdown.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const starCount = await getFormattedStars('kamranahmedse/developer-roadmap');
|
||||
|
||||
<div class='py-6 sm:py-16 border-b border-t text-left sm:text-center bg-white'>
|
||||
<div class='!max-w-[600px] container'>
|
||||
<p class='text-2xl sm:text-5xl font-bold'>Community</p>
|
||||
<h2 class='text-2xl sm:text-5xl font-bold'>Community</h2>
|
||||
<p class='text-gray-600 text-sm sm:text-lg leading-relaxed my-2.5 sm:my-5'>
|
||||
roadmap.sh is the <a
|
||||
href='https://github.com/search?o=desc&q=stars%3A%3E100000&s=stars&type=Repositories'
|
||||
|
||||
@@ -5,9 +5,9 @@ import Popup from './Popup/Popup.astro';
|
||||
|
||||
<Popup id='progress-help' title='' subtitle=''>
|
||||
<div class='-mt-2.5'>
|
||||
<p class='mb-3 text-2xl font-semibold leading-5 text-gray-900'>
|
||||
<h2 class='mb-3 text-2xl font-semibold leading-5 text-gray-900'>
|
||||
Track your Progress
|
||||
</p>
|
||||
</h2>
|
||||
<p class='text-sm leading-4 text-gray-600'>
|
||||
Login and use one of the options listed below.
|
||||
</p>
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/**
|
||||
* atom-dark theme for `prism.js`
|
||||
* Based on Atom's `atom-dark` theme: https://github.com/atom/atom-dark-syntax
|
||||
* @author Joe Gibson (@gibsjose)
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #c5c8c6;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||
font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier, monospace;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #1d1f21;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #7C7C7C;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #c5c8c6;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.keyword,
|
||||
.token.tag {
|
||||
color: #96CBFE;
|
||||
}
|
||||
|
||||
.token.class-name {
|
||||
color: #FFFFB6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.token.boolean,
|
||||
.token.constant {
|
||||
color: #99CC99;
|
||||
}
|
||||
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #FF73FD;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #A8FF60;
|
||||
}
|
||||
|
||||
.token.variable {
|
||||
color: #C6C5FE;
|
||||
}
|
||||
|
||||
.token.operator {
|
||||
color: #EDEDED;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
color: #FFFFB6;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.url {
|
||||
color: #96CBFE;
|
||||
}
|
||||
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #87C38A;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value {
|
||||
color: #F9EE98;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #DAD085;
|
||||
}
|
||||
|
||||
.token.regex {
|
||||
color: #E9C062;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import type { QuestionType } from '../../lib/question-group';
|
||||
import { markdownToHtml } from '../../lib/markdown';
|
||||
import Prism from 'prismjs';
|
||||
import './PrismAtom.css';
|
||||
|
||||
type QuestionCardProps = {
|
||||
question: QuestionType;
|
||||
};
|
||||
|
||||
export function QuestionCard(props: QuestionCardProps) {
|
||||
const { question } = props;
|
||||
|
||||
const [isAnswerVisible, setIsAnswerVisible] = useState<boolean>(false);
|
||||
const answerRef = useRef<HTMLDivElement>(null);
|
||||
const questionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// set the height of the question width to the height of the answer
|
||||
// width if the answer is visible and the question height is less than
|
||||
// the answer height
|
||||
if (isAnswerVisible) {
|
||||
Prism.highlightAll();
|
||||
|
||||
const answerHeight = answerRef.current?.clientHeight || 0;
|
||||
const questionHeight = questionRef.current?.clientHeight || 0;
|
||||
|
||||
if (answerHeight > questionHeight) {
|
||||
questionRef.current!.style.height = `${answerHeight}px`;
|
||||
}
|
||||
} else {
|
||||
questionRef.current!.style.height = `auto`;
|
||||
}
|
||||
|
||||
// if the user has scrolled down and the top of the answer is not
|
||||
// visible, scroll to the top of the answer
|
||||
const questionTop =
|
||||
(questionRef.current?.getBoundingClientRect().top || 0) - 147;
|
||||
if (questionTop < 0) {
|
||||
window.scrollTo({
|
||||
top: window.scrollY + questionTop - 10,
|
||||
});
|
||||
}
|
||||
}, [isAnswerVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsAnswerVisible(false);
|
||||
}, [question]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={questionRef}
|
||||
className={`flex flex-grow flex-col items-center justify-center py-5 sm:py-8`}
|
||||
>
|
||||
<div className="hidden text-gray-400 sm:block">
|
||||
{question.topics?.map((topic, counter) => {
|
||||
const totalTopics = question.topics?.length || 0;
|
||||
|
||||
return (
|
||||
<Fragment key={topic}>
|
||||
<span className="capitalize">{topic}</span>
|
||||
{counter !== totalTopics - 1 && (
|
||||
<span className="mx-2">·</span>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="mx-auto flex max-w-[550px] flex-1 items-center justify-center py-3 sm:py-8">
|
||||
<p className="px-4 text-xl font-semibold !leading-snug text-black sm:text-3xl">
|
||||
{question.question}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAnswerVisible(true);
|
||||
}}
|
||||
className="cursor-pointer text-sm text-gray-500 underline underline-offset-4 transition-colors hover:text-black sm:text-base"
|
||||
>
|
||||
Click to Reveal the Answer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={answerRef}
|
||||
className={`absolute left-0 right-0 flex flex-col items-center justify-center rounded-[7px] bg-neutral-100 py-4 text-sm leading-normal text-black transition-all duration-300 sm:py-8 sm:text-xl ${
|
||||
isAnswerVisible ? 'top-0 min-h-[248px] sm:min-h-[398px]' : 'top-full'
|
||||
}`}
|
||||
>
|
||||
{!question.isLongAnswer && (
|
||||
<div
|
||||
className={`mx-auto flex max-w-[600px] flex-grow flex-col items-center justify-center py-0 px-5 text-center text-base [&>p]:leading-relaxed sm:text-xl`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: markdownToHtml(question.answer, false),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{question.isLongAnswer && (
|
||||
<div
|
||||
className={`qa-answer prose prose-sm prose-quoteless mx-auto flex w-full max-w-[600px] flex-grow flex-col items-start justify-center py-0 px-4 text-left text-sm prose-h1:mb-2.5 prose-h1:mt-7 prose-h2:mb-3 prose-h2:mt-0 prose-h3:mb-[5px] prose-h3:mt-[10px] prose-p:mb-2 prose-p:mt-0 prose-blockquote:font-normal prose-blockquote:not-italic prose-blockquote:text-gray-700 prose-pre:!mb-6 prose-pre:w-full prose-ul:my-2 prose-li:m-0 prose-li:mb-0.5 sm:px-5 sm:text-lg sm:prose-p:mb-4`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: markdownToHtml(question.answer, false),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-7 text-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAnswerVisible(false);
|
||||
}}
|
||||
className="cursor-pointer text-sm text-gray-500 underline underline-offset-4 transition-colors hover:text-black sm:text-base"
|
||||
>
|
||||
Hide the Answer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
PartyPopper,
|
||||
RefreshCcw,
|
||||
SkipForward,
|
||||
Sparkles,
|
||||
ThumbsUp,
|
||||
} from 'lucide-react';
|
||||
import type { QuestionProgressType } from './QuestionsList';
|
||||
|
||||
type ProgressStatButtonProps = {
|
||||
isDisabled?: boolean;
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
count: number;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
function ProgressStatButton(props: ProgressStatButtonProps) {
|
||||
const { icon, label, count, onClick, isDisabled = false } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={isDisabled}
|
||||
onClick={onClick}
|
||||
className="group relative text-sm sm:text-base flex flex-1 items-center overflow-hidden rounded-md sm:rounded-xl border border-gray-300 bg-white py-2 px-2 sm:py-3 sm:px-4 text-black transition-colors hover:border-black disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
{icon}
|
||||
<span className="flex flex-grow justify-between">
|
||||
<span>{label}</span>
|
||||
<span>{count}</span>
|
||||
</span>
|
||||
|
||||
<span className="absolute top-full left-0 right-0 flex h-full items-center justify-center border border-black bg-black text-white transition-all duration-200 group-hover:top-0">
|
||||
Restart Asking
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
type QuestionFinishedProps = {
|
||||
knowCount: number;
|
||||
didNotKnowCount: number;
|
||||
skippedCount: number;
|
||||
totalCount: number;
|
||||
onReset: (type: QuestionProgressType | 'reset') => void;
|
||||
};
|
||||
|
||||
export function QuestionFinished(props: QuestionFinishedProps) {
|
||||
const { knowCount, didNotKnowCount, skippedCount, totalCount, onReset } =
|
||||
props;
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-grow flex-col items-center justify-center px-4 sm:px-0">
|
||||
<PartyPopper className="mb-4 mt-10 h-14 w-14 text-gray-300 sm:mt-0 sm:h-24 sm:w-24" />
|
||||
<h1 className="text-lg font-semibold text-gray-700 sm:text-2xl">
|
||||
Questions Finished
|
||||
</h1>
|
||||
<p className="mt-0 text-sm text-gray-500 sm:mt-2 sm:text-base">
|
||||
Click below revisit{' '}
|
||||
<span className="hidden sm:inline">specific or all questions</span>{' '}
|
||||
<span className="inline sm:hidden">questions</span>
|
||||
</p>
|
||||
|
||||
<div className="mt-5 mb-5 flex w-full flex-col gap-1.5 sm:gap-3 px-2 sm:flex-row sm:px-16">
|
||||
<ProgressStatButton
|
||||
icon={<ThumbsUp className="mr-1 h-4" />}
|
||||
label="Knew"
|
||||
count={knowCount}
|
||||
isDisabled={knowCount === 0}
|
||||
onClick={() => onReset('know')}
|
||||
/>
|
||||
<ProgressStatButton
|
||||
icon={<Sparkles className="mr-1 h-4" />}
|
||||
label="Learned"
|
||||
count={didNotKnowCount}
|
||||
isDisabled={didNotKnowCount === 0}
|
||||
onClick={() => onReset('dontKnow')}
|
||||
/>
|
||||
<ProgressStatButton
|
||||
icon={<SkipForward className="mr-1 h-4" />}
|
||||
label="Skipped"
|
||||
count={skippedCount}
|
||||
isDisabled={skippedCount === 0}
|
||||
onClick={() => onReset('skip')}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 mb-4 sm:mb-0 text-sm">
|
||||
<button
|
||||
onClick={() => onReset('reset')}
|
||||
className="flex items-center gap-0.5 text-red-700 hover:text-black text-sm sm:text-base"
|
||||
>
|
||||
<RefreshCcw className="mr-1 h-4" />
|
||||
Restart Asking
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import {Spinner} from "../ReactIcons/Spinner";
|
||||
|
||||
export function QuestionLoader() {
|
||||
return (
|
||||
<div className="flex flex-grow flex-col items-center justify-center">
|
||||
<p className="text-xl font-medium text-gray-500 flex items-center gap-3.5">
|
||||
<Spinner isDualRing={false} innerFill='#6b7280' className="h-5 w-5" />
|
||||
Please wait ..
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { QuestionsProgress } from './QuestionsProgress';
|
||||
import { CheckCircle, SkipForward, Sparkles } from 'lucide-react';
|
||||
import { QuestionCard } from './QuestionCard';
|
||||
import { QuestionLoader } from './QuestionLoader';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import type { QuestionType } from '../../lib/question-group';
|
||||
import { httpGet, httpPut } from '../../lib/http';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { QuestionFinished } from './QuestionFinished';
|
||||
import { Confetti } from '../Confetti';
|
||||
|
||||
type UserQuestionProgress = {
|
||||
know: string[];
|
||||
dontKnow: string[];
|
||||
skip: string[];
|
||||
};
|
||||
|
||||
export type QuestionProgressType = keyof UserQuestionProgress;
|
||||
|
||||
type QuestionsListProps = {
|
||||
groupId: string;
|
||||
questions: QuestionType[];
|
||||
};
|
||||
|
||||
export function QuestionsList(props: QuestionsListProps) {
|
||||
const { questions: unshuffledQuestions, groupId } = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showConfetti, setShowConfetti] = useState(false);
|
||||
const [questions, setQuestions] = useState<QuestionType[]>();
|
||||
const [pendingQuestions, setPendingQuestions] = useState<QuestionType[]>([]);
|
||||
|
||||
const [userProgress, setUserProgress] = useState<UserQuestionProgress>();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
async function fetchUserProgress(): Promise<
|
||||
UserQuestionProgress | undefined
|
||||
> {
|
||||
if (!isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { response, error } = await httpGet<UserQuestionProgress>(
|
||||
`${
|
||||
import.meta.env.PUBLIC_API_URL
|
||||
}/v1-get-user-question-progress/${groupId}`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message || 'Error fetching user progress');
|
||||
return;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function loadQuestions() {
|
||||
const userProgress = await fetchUserProgress();
|
||||
setUserProgress(userProgress);
|
||||
|
||||
const knownQuestions = userProgress?.know || [];
|
||||
const didNotKnowQuestions = userProgress?.dontKnow || [];
|
||||
const skipQuestions = userProgress?.skip || [];
|
||||
|
||||
const pendingQuestions = unshuffledQuestions.filter((question) => {
|
||||
return (
|
||||
!knownQuestions.includes(question.id) &&
|
||||
!didNotKnowQuestions.includes(question.id) &&
|
||||
!skipQuestions.includes(question.id)
|
||||
);
|
||||
});
|
||||
|
||||
// Shuffle and set pending questions
|
||||
setPendingQuestions(pendingQuestions.sort(() => Math.random() - 0.5));
|
||||
setQuestions(unshuffledQuestions);
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
async function resetProgress(type: QuestionProgressType | 'reset' = 'reset') {
|
||||
let knownQuestions = userProgress?.know || [];
|
||||
let didNotKnowQuestions = userProgress?.dontKnow || [];
|
||||
let skipQuestions = userProgress?.skip || [];
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
if (type === 'know') {
|
||||
knownQuestions = [];
|
||||
} else if (type === 'dontKnow') {
|
||||
didNotKnowQuestions = [];
|
||||
} else if (type === 'skip') {
|
||||
skipQuestions = [];
|
||||
} else if (type === 'reset') {
|
||||
knownQuestions = [];
|
||||
didNotKnowQuestions = [];
|
||||
skipQuestions = [];
|
||||
}
|
||||
} else {
|
||||
setIsLoading(true);
|
||||
|
||||
const { response, error } = await httpPut<UserQuestionProgress>(
|
||||
`${
|
||||
import.meta.env.PUBLIC_API_URL
|
||||
}/v1-reset-question-progress/${groupId}`,
|
||||
{
|
||||
status: type,
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message || 'Error resetting progress');
|
||||
return;
|
||||
}
|
||||
|
||||
knownQuestions = response?.know || [];
|
||||
didNotKnowQuestions = response?.dontKnow || [];
|
||||
skipQuestions = response?.skip || [];
|
||||
}
|
||||
|
||||
const pendingQuestions = unshuffledQuestions.filter((question) => {
|
||||
return (
|
||||
!knownQuestions.includes(question.id) &&
|
||||
!didNotKnowQuestions.includes(question.id) &&
|
||||
!skipQuestions.includes(question.id)
|
||||
);
|
||||
});
|
||||
|
||||
setUserProgress({
|
||||
know: knownQuestions,
|
||||
dontKnow: didNotKnowQuestions,
|
||||
skip: skipQuestions,
|
||||
});
|
||||
|
||||
setPendingQuestions(pendingQuestions.sort(() => Math.random() - 0.5));
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
async function updateQuestionStatus(
|
||||
status: QuestionProgressType,
|
||||
questionId: string
|
||||
) {
|
||||
setIsLoading(true);
|
||||
let newProgress = userProgress || { know: [], dontKnow: [], skip: [] };
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
if (status === 'know') {
|
||||
newProgress.know.push(questionId);
|
||||
} else if (status == 'dontKnow') {
|
||||
newProgress.dontKnow.push(questionId);
|
||||
} else if (status == 'skip') {
|
||||
newProgress.skip.push(questionId);
|
||||
}
|
||||
} else {
|
||||
const { response, error } = await httpPut<UserQuestionProgress>(
|
||||
`${
|
||||
import.meta.env.PUBLIC_API_URL
|
||||
}/v1-update-question-status/${groupId}`,
|
||||
{
|
||||
status,
|
||||
questionId,
|
||||
questionGroupId: groupId,
|
||||
}
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Error marking question status');
|
||||
return;
|
||||
}
|
||||
|
||||
newProgress = response;
|
||||
}
|
||||
|
||||
const updatedQuestionList = pendingQuestions.filter(
|
||||
(q) => q.id !== questionId
|
||||
);
|
||||
|
||||
setUserProgress(newProgress);
|
||||
setPendingQuestions(updatedQuestionList);
|
||||
setIsLoading(false);
|
||||
|
||||
if (updatedQuestionList.length === 0) {
|
||||
setShowConfetti(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadQuestions().then(() => null);
|
||||
}, [unshuffledQuestions]);
|
||||
|
||||
const knowCount = userProgress?.know.length || 0;
|
||||
const dontKnowCount = userProgress?.dontKnow.length || 0;
|
||||
const skipCount = userProgress?.skip.length || 0;
|
||||
const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0;
|
||||
|
||||
const currQuestion = pendingQuestions[0];
|
||||
const hasFinished = !isLoading && hasProgress && !currQuestion;
|
||||
|
||||
return (
|
||||
<div className="mb-0 sm:mb-40 gap-3 text-center">
|
||||
<QuestionsProgress
|
||||
knowCount={knowCount}
|
||||
didNotKnowCount={dontKnowCount}
|
||||
skippedCount={skipCount}
|
||||
totalCount={unshuffledQuestions?.length || questions?.length}
|
||||
isLoading={isLoading}
|
||||
showLoginAlert={!isLoggedIn() && hasProgress}
|
||||
onResetClick={() => {
|
||||
resetProgress('reset').finally(() => null);
|
||||
}}
|
||||
/>
|
||||
|
||||
{showConfetti && containerRef.current && (
|
||||
<Confetti
|
||||
pieces={100}
|
||||
element={containerRef.current}
|
||||
onDone={() => {
|
||||
setShowConfetti(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative mb-4 flex min-h-[250px] w-full overflow-hidden rounded-lg border border-gray-300 bg-white sm:min-h-[400px]"
|
||||
>
|
||||
{hasFinished && (
|
||||
<QuestionFinished
|
||||
totalCount={unshuffledQuestions?.length || questions?.length || 0}
|
||||
knowCount={knowCount}
|
||||
didNotKnowCount={dontKnowCount}
|
||||
skippedCount={skipCount}
|
||||
onReset={(type: QuestionProgressType | 'reset') => {
|
||||
resetProgress(type).finally(() => null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && currQuestion && <QuestionCard question={currQuestion} />}
|
||||
{isLoading && <QuestionLoader />}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`flex flex-col gap-1 sm:gap-3 transition-opacity duration-300 sm:flex-row ${
|
||||
hasFinished ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
disabled={isLoading || !currQuestion}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault()
|
||||
updateQuestionStatus('know', currQuestion.id).finally(() => null);
|
||||
}}
|
||||
className="flex flex-1 items-center rounded-md sm:rounded-lg border border-gray-300 bg-white text-sm sm:text-base py-2 px-2 sm:py-3 sm:px-4 text-black transition-colors hover:border-black hover:bg-black hover:text-white disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<CheckCircle className="mr-1 h-4 text-current" />
|
||||
Already Know that
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
updateQuestionStatus('dontKnow', currQuestion.id).finally(
|
||||
() => null
|
||||
);
|
||||
}}
|
||||
disabled={isLoading || !currQuestion}
|
||||
className="flex flex-1 items-center rounded-md sm:rounded-lg border border-gray-300 bg-white text-sm sm:text-base py-2 px-2 sm:py-3 sm:px-4 text-black transition-colors hover:border-black hover:bg-black hover:text-white disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<Sparkles className="mr-1 h-4 text-current" />
|
||||
Didn't Know that
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
updateQuestionStatus('skip', currQuestion.id).finally(() => null);
|
||||
}}
|
||||
disabled={isLoading || !currQuestion}
|
||||
data-next-question="skip"
|
||||
className="flex flex-1 items-center rounded-md sm:rounded-lg border border-red-600 text-sm sm:text-base py-2 px-2 sm:py-3 sm:px-4 text-red-600 hover:bg-red-600 hover:text-white disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<SkipForward className="mr-1 h-4" />
|
||||
Skip Question
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import { CheckCircle, RotateCcw, SkipForward, Sparkles } from 'lucide-react';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
|
||||
type QuestionsProgressProps = {
|
||||
isLoading?: boolean;
|
||||
showLoginAlert?: boolean;
|
||||
knowCount?: number;
|
||||
didNotKnowCount?: number;
|
||||
totalCount?: number;
|
||||
skippedCount?: number;
|
||||
onResetClick?: () => void;
|
||||
};
|
||||
|
||||
export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
const {
|
||||
showLoginAlert,
|
||||
isLoading = false,
|
||||
knowCount = 0,
|
||||
didNotKnowCount = 0,
|
||||
totalCount = 0,
|
||||
skippedCount = 0,
|
||||
onResetClick = () => null,
|
||||
} = props;
|
||||
|
||||
const totalSolved = knowCount + didNotKnowCount + skippedCount;
|
||||
const donePercentage = (totalSolved / totalCount) * 100;
|
||||
|
||||
return (
|
||||
<div className="mb-3 sm:mb-5 overflow-hidden rounded-lg border border-gray-300 bg-white p-4 sm:p-6">
|
||||
<div className="mb-3 flex items-center text-gray-600">
|
||||
<div className="relative w-full flex-1 rounded-xl bg-gray-200 p-1">
|
||||
<div
|
||||
className="duration-400 absolute bottom-0 left-0 top-0 rounded-xl bg-slate-800 transition-[width]"
|
||||
style={{
|
||||
width: `${donePercentage}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-3 text-sm">
|
||||
{totalSolved} / {totalCount}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative -left-1 flex flex-col gap-2 text-sm text-black sm:flex-row sm:gap-3">
|
||||
<span className="flex items-center">
|
||||
<CheckCircle className="mr-1 h-4" />
|
||||
<span>Knew</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
<span className="tabular-nums">{knowCount}</span>{' '}
|
||||
<span className="hidden lg:inline">Questions</span>
|
||||
<span className="inline sm:hidden">Questions</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className="flex items-center">
|
||||
<Sparkles className="mr-1 h-4" />
|
||||
<span>Learnt</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
<span className="tabular-nums">{didNotKnowCount}</span>{' '}
|
||||
<span className="hidden lg:inline">Questions</span>
|
||||
<span className="inline sm:hidden">Questions</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className="flex items-center">
|
||||
<SkipForward className="mr-1 h-4" />
|
||||
<span>Skipped</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
<span className="tabular-nums">{skippedCount}</span>{' '}
|
||||
<span className="hidden lg:inline">Questions</span>
|
||||
<span className="inline sm:hidden">Questions</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<button
|
||||
disabled={isLoading}
|
||||
onClick={onResetClick}
|
||||
className="flex items-center text-red-600 transition-opacity duration-300 hover:text-red-900 disabled:opacity-50"
|
||||
>
|
||||
<RotateCcw className="mr-1 h-4" />
|
||||
Reset
|
||||
<span className='inline lg:hidden'>Progress</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showLoginAlert && (
|
||||
<p className="-mx-6 mt-6 -mb-6 border-t bg-yellow-100 py-3 text-sm text-yellow-900">
|
||||
You progress is not saved. Please{' '}
|
||||
<button
|
||||
onClick={() => {
|
||||
showLoginPopup();
|
||||
}}
|
||||
className="underline-offset-3 font-medium underline hover:text-black"
|
||||
>
|
||||
login to save your progress.
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ const relatedRoadmapDetails = await getRoadmapsByIds(relatedRoadmaps);
|
||||
<div class='border-t bg-gray-100'>
|
||||
<div class='container'>
|
||||
<div class='flex justify-between relative -top-5'>
|
||||
<span class='text-md font-medium py-1 px-3 border bg-white rounded-md'>Related Roadmaps</span>
|
||||
<h2 class='text-md font-medium py-1 px-3 border bg-white rounded-md'>Related Roadmaps</h2>
|
||||
<a href='/roadmaps' class='text-md font-medium py-1 px-3 border bg-white rounded-md hover:bg-gray-50'>
|
||||
<span class='hidden sm:inline'>All Roadmaps →</span>
|
||||
<span class='inline sm:hidden'>More →</span>
|
||||
|
||||
@@ -5,10 +5,10 @@ import { ProgressShareButton } from './UserProgress/ProgressShareButton';
|
||||
export interface Props {
|
||||
resourceId: string;
|
||||
resourceType: ResourceType;
|
||||
hasSecondaryBanner?: boolean;
|
||||
isSecondaryBanner?: boolean;
|
||||
}
|
||||
|
||||
const { hasSecondaryBanner = false, resourceId, resourceType } = Astro.props;
|
||||
const { isSecondaryBanner = false, resourceId, resourceType } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
@@ -16,8 +16,8 @@ const { hasSecondaryBanner = false, resourceId, resourceType } = Astro.props;
|
||||
class:list={[
|
||||
'hidden sm:flex justify-between px-2 bg-white items-center py-1.5 relative striped-loader bg-white',
|
||||
{
|
||||
'rounded-tl-md rounded-tr-md': hasSecondaryBanner,
|
||||
'rounded-md': !hasSecondaryBanner,
|
||||
'rounded-bl-md rounded-br-md': isSecondaryBanner,
|
||||
'rounded-md': !isSecondaryBanner,
|
||||
},
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,6 @@ import YouTubeAlert from './YouTubeAlert.astro';
|
||||
import ProgressHelpPopup from './ProgressHelpPopup.astro';
|
||||
import { MarkFavorite } from './FeaturedItems/MarkFavorite';
|
||||
import { TeamVersions } from './TeamVersions/TeamVersions';
|
||||
import { RoadmapFrontmatter } from '../lib/roadmap';
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
@@ -18,7 +17,6 @@ export interface Props {
|
||||
roadmapId: string;
|
||||
isUpcoming?: boolean;
|
||||
hasSearch?: boolean;
|
||||
question?: RoadmapFrontmatter['question'];
|
||||
hasTopics?: boolean;
|
||||
}
|
||||
|
||||
@@ -31,43 +29,25 @@ const {
|
||||
hasSearch = false,
|
||||
note,
|
||||
hasTopics = false,
|
||||
question,
|
||||
} = Astro.props;
|
||||
|
||||
const isRoadmapReady = !isUpcoming;
|
||||
|
||||
const roadmapTitle =
|
||||
roadmapId === 'devops'
|
||||
? 'DevOps'
|
||||
: `${roadmapId.charAt(0).toUpperCase()}${roadmapId.slice(1)}`;
|
||||
|
||||
const hasTnsBanner = !!tnsBannerLink;
|
||||
---
|
||||
|
||||
<LoginPopup />
|
||||
<ProgressHelpPopup />
|
||||
|
||||
<div class='relative border-b'>
|
||||
<div
|
||||
class:list={[
|
||||
'container relative py-5',
|
||||
{
|
||||
'sm:py-16': hasTnsBanner,
|
||||
'sm:py-12': !hasTnsBanner,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div class='border-b'>
|
||||
<div class='container relative py-5 sm:py-12'>
|
||||
<div class='mb-3 mt-0 sm:mb-4'>
|
||||
<h1 class='mb-0.5 text-2xl font-bold sm:mb-2 sm:text-4xl'>
|
||||
{title}
|
||||
<span class='relative top-0 sm:-top-1'>
|
||||
<MarkFavorite
|
||||
resourceId={roadmapId}
|
||||
resourceType='roadmap'
|
||||
className='text-gray-500 !opacity-100 hover:text-gray-600 [&>svg]:stroke-[0.4] [&>svg]:stroke-gray-400 hover:[&>svg]:stroke-gray-600 [&>svg]:h-4 [&>svg]:w-4 sm:[&>svg]:h-4 sm:[&>svg]:w-4 ml-1.5 relative focus:outline-0'
|
||||
client:only='react'
|
||||
/>
|
||||
</span>
|
||||
<MarkFavorite
|
||||
resourceId={roadmapId}
|
||||
resourceType='roadmap'
|
||||
className='text-gray-500 !opacity-100 hover:text-gray-600 [&>svg]:stroke-[0.4] [&>svg]:stroke-gray-400 hover:[&>svg]:stroke-gray-600 [&>svg]:h-4 [&>svg]:w-4 sm:[&>svg]:h-5 sm:[&>svg]:w-5 ml-1.5 relative focus:outline-0'
|
||||
client:only="react"
|
||||
/>
|
||||
</h1>
|
||||
<p class='text-sm text-gray-500 sm:text-lg'>{description}</p>
|
||||
</div>
|
||||
@@ -141,7 +121,7 @@ const hasTnsBanner = !!tnsBannerLink;
|
||||
<TeamVersions
|
||||
resourceType='roadmap'
|
||||
resourceId={roadmapId}
|
||||
client:only='react'
|
||||
client:only
|
||||
/>
|
||||
|
||||
{
|
||||
@@ -164,31 +144,12 @@ const hasTnsBanner = !!tnsBannerLink;
|
||||
<!-- Desktop: Roadmap Resources - Alert -->
|
||||
{
|
||||
hasTopics && (
|
||||
<RoadmapHint
|
||||
tnsBannerLink={tnsBannerLink}
|
||||
titleQuestion={question?.title}
|
||||
titleAnswer={question?.description}
|
||||
roadmapId={roadmapId}
|
||||
/>
|
||||
<RoadmapHint roadmapId={roadmapId} tnsBannerLink={tnsBannerLink} />
|
||||
)
|
||||
}
|
||||
|
||||
{hasSearch && <TopicSearch />}
|
||||
</div>
|
||||
|
||||
{
|
||||
tnsBannerLink && (
|
||||
<div class='absolute left-0 right-0 top-0 hidden border-b border-b-gray-200 px-2 py-1.5 sm:block'>
|
||||
<p class='py-0.5 text-center text-sm'>
|
||||
<span class='badge mr-1'>Partner</span>
|
||||
Get the latest {roadmapTitle} news from our sister site{' '}
|
||||
<a href={tnsBannerLink} target='_blank' class='font-medium underline'>
|
||||
TheNewStack.io
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{note && <RoadmapNote text={note} />}
|
||||
|
||||
@@ -1,57 +1,47 @@
|
||||
---
|
||||
import AstroIcon from './AstroIcon.astro';
|
||||
import Icon from './AstroIcon.astro';
|
||||
import { RoadmapTitleQuestion } from './RoadmapTitleQuestion.tsx';
|
||||
import ResourceProgressStats from './ResourceProgressStats.astro';
|
||||
|
||||
export interface Props {
|
||||
roadmapId: string;
|
||||
tnsBannerLink?: string;
|
||||
titleQuestion?: string;
|
||||
titleAnswer?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
roadmapId,
|
||||
titleQuestion = '',
|
||||
titleAnswer = '',
|
||||
tnsBannerLink,
|
||||
} = Astro.props;
|
||||
const hasTitleQuestion = titleQuestion && titleAnswer;
|
||||
const hasTnsBanner = !!tnsBannerLink;
|
||||
const { roadmapId, tnsBannerLink = '' } = Astro.props;
|
||||
|
||||
const hasTNSBanner = !!tnsBannerLink;
|
||||
const roadmapTitle =
|
||||
roadmapId === 'devops'
|
||||
? 'DevOps'
|
||||
: `${roadmapId.charAt(0).toUpperCase()}${roadmapId.slice(1)}`;
|
||||
---
|
||||
|
||||
<div
|
||||
class:list={[
|
||||
'mt-4 sm:mt-7 border-0 sm:border rounded-md mb-0 bg-white',
|
||||
...(hasTnsBanner
|
||||
? [
|
||||
{
|
||||
'sm:-mb-[110px]': hasTitleQuestion,
|
||||
'sm:-mb-[81px]': !hasTitleQuestion,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
'sm:-mb-[88px]': hasTitleQuestion,
|
||||
'sm:-mb-[65px]': !hasTitleQuestion,
|
||||
},
|
||||
]),
|
||||
'mt-4 sm:mt-7 border-0 sm:border rounded-md mb-0',
|
||||
{
|
||||
'sm:-mb-[82px]': hasTNSBanner,
|
||||
'sm:-mb-[65px]': !hasTNSBanner,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ResourceProgressStats
|
||||
resourceId={roadmapId}
|
||||
resourceType='roadmap'
|
||||
hasSecondaryBanner={hasTitleQuestion}
|
||||
/>
|
||||
|
||||
{
|
||||
hasTitleQuestion && (
|
||||
<RoadmapTitleQuestion
|
||||
client:load
|
||||
question={titleQuestion}
|
||||
answer={titleAnswer}
|
||||
/>
|
||||
hasTNSBanner && (
|
||||
<div class='hidden border-b bg-gray-100 px-2 py-1.5 sm:block'>
|
||||
<p class='text-sm'>
|
||||
Get the latest {roadmapTitle} news from our sister site{' '}
|
||||
<a
|
||||
href={tnsBannerLink}
|
||||
target='_blank'
|
||||
class='font-semibold underline'
|
||||
>
|
||||
TheNewStack.io
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<ResourceProgressStats isSecondaryBanner={hasTNSBanner} resourceId={roadmapId} resourceType='roadmap' />
|
||||
</div>
|
||||
@@ -1,70 +0,0 @@
|
||||
import { ChevronDown, ChevronUp, GraduationCap } from 'lucide-react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOutsideClick } from '../hooks/use-outside-click';
|
||||
import { markdownToHtml } from '../lib/markdown';
|
||||
|
||||
type RoadmapTitleQuestionProps = {
|
||||
question: string;
|
||||
answer: string;
|
||||
};
|
||||
|
||||
export function RoadmapTitleQuestion(props: RoadmapTitleQuestionProps) {
|
||||
const { question, answer } = props;
|
||||
|
||||
const [isAnswerVisible, setIsAnswerVisible] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useOutsideClick(ref, () => {
|
||||
setIsAnswerVisible(false);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative hidden border-t text-sm font-medium sm:block">
|
||||
{isAnswerVisible && (
|
||||
<div className="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50"></div>
|
||||
)}
|
||||
<h2
|
||||
className="z-50 flex cursor-pointer items-center px-2 py-2.5 font-medium text-base"
|
||||
aria-expanded={isAnswerVisible ? 'true' : 'false'}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsAnswerVisible(!isAnswerVisible);
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center flex-grow">
|
||||
<GraduationCap className="mr-2 inline-block h-6 w-6" />
|
||||
{question}
|
||||
</span>
|
||||
<span className="flex-shrink-0 text-gray-400">
|
||||
<ChevronDown className={`inline-block h-5 w-5`} />
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className={`absolute left-0 right-0 top-0 z-50 mt-0 rounded-md border bg-white ${
|
||||
isAnswerVisible ? 'block' : 'hidden'
|
||||
}`}
|
||||
ref={ref}
|
||||
>
|
||||
{isAnswerVisible && (
|
||||
<h2
|
||||
className="flex cursor-pointer items-center border-b px-[7px] py-[9px] text-base font-medium"
|
||||
onClick={() => setIsAnswerVisible(false)}
|
||||
>
|
||||
<span className="flex flex-grow items-center">
|
||||
<GraduationCap className="mr-2 inline-block h-6 w-6" />
|
||||
{question}
|
||||
</span>
|
||||
<span className="flex-shrink-0 text-gray-400">
|
||||
<ChevronUp className={`inline-block h-5 w-5`} />
|
||||
</span>
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
className="bg-gray-100 [&>p]:text-gray-800 p-3 text-base [&>h2]:mb-2 [&>h2]:mt-5 [&>h2]:text-[17px] [&>h2]:font-medium [&>p:last-child]:mb-0 [&>p>a]:font-semibold [&>p>a]:underline [&>p>a]:underline-offset-2 [&>p]:mb-3 [&>p]:font-normal [&>p]:leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: markdownToHtml(answer, false) }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
> Set up automated security auditing.
|
||||
|
||||
It's important to keep track of changes in your infrastructure's security settings. One way to do this is to first set up a security auditer role ([JSON template](https://gist.github.com/bigsnarfdude/d0758b4fd335085623be)), which will give anyone assigned that role read-only access to any security related settings on your account. You can then use this rather [fantastic Python script](https://gist.github.com/jlevy/cce1b44fc24f94599d0a4b3e613cc15d), which will go over all the items in your account and produce a canonical output showing your configuration. You set up a cronjob somewhere to run this script, and compare its output to the output from the previous run. Any differences will show you exactly what has been changed in your security configuration. It's useful to set this up and just have it email you the diff of any changes. (Source: Intrusion Detection in the Cloud - [Presentation](https://awsmedia.s3.amazonaws.com/SEC402.pdf))
|
||||
It's important to keep track of changes in your infrastructure's security settings. One way to do this is to first set up a security auditer role ([JSON template](https://gist.github.com/bigsnarfdude/d0758b4fd335085623be)), which will give anyone assigned that role read-only access to any security related settings on your account. You can then use this rather [fantastic Python script](https://gist.github.com/jlevy/cce1b44fc24f94599d0a4b3e613cc15d), which will go over all the items in your account and produce a canonical output showing your configuration. You set up a cronjob somewhere to run this script, and compare its output to the output from the previous run. Any differences will show you exactly what has been changed in your security configuration. It's useful to set this up and just have it email you the diff of any changes. (Source: Intrusion Detection in the Cloud - [Presentation](http://awsmedia.s3.amazonaws.com/SEC402.pdf))
|
||||
|
||||
@@ -6,6 +6,6 @@ Removing all unnecessary spaces, comments and attributes will reduce the size of
|
||||
|
||||
Most of the frameworks have plugins to facilitate the minification of the webpages. You can use a bunch of NPM modules that can do the job for you automatically.
|
||||
|
||||
- [HTML minifier | Code Beautify](https://codebeautify.org/minify-html)
|
||||
- [HTML minifier | Minify Code](http://minifycode.com/html-minifier/)
|
||||
- [Online HTML Compressor](http://refresh-sf.com)
|
||||
- [Experimenting with HTML minifier — Perfection Kills](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype)
|
||||
|
||||
12
src/data/question-groups/backend/backend.json
Normal file
12
src/data/question-groups/backend/backend.json
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": "what-is-server",
|
||||
"question": "What is a Server",
|
||||
"answer": {
|
||||
"heading": "",
|
||||
"answer": "",
|
||||
"list": [],
|
||||
"file": "what-is-server.md"
|
||||
}
|
||||
}
|
||||
]
|
||||
1
src/data/question-groups/backend/what-is-server.md
Normal file
1
src/data/question-groups/backend/what-is-server.md
Normal file
@@ -0,0 +1 @@
|
||||
## What is Server
|
||||
@@ -1,27 +0,0 @@
|
||||
Let's see how we can use the `alert`, `prompt` and `confirm` functions to interact with the user.
|
||||
|
||||
## alert()
|
||||
|
||||
The `alert()` method displays an alert box with a specified message and an OK button.
|
||||
|
||||
```js
|
||||
alert('Hello World!');
|
||||
```
|
||||
|
||||
## prompt()
|
||||
|
||||
The `prompt()` method displays a dialog box that prompts the visitor for input. A prompt box is often used if you want the user to input a value before entering a page. The `prompt()` method returns the input value if the user clicks OK. If the user clicks Cancel, the method returns `null`.
|
||||
|
||||
```js
|
||||
const name = prompt('What is your name?');
|
||||
console.log(name);
|
||||
```
|
||||
|
||||
## confirm()
|
||||
|
||||
The `confirm()` method displays a dialog box with a specified message, along with an OK and a Cancel button. This is often used to confirm or verify something from the user.
|
||||
|
||||
```js
|
||||
const result = confirm('Are you sure?');
|
||||
console.log(result); // true/false
|
||||
```
|
||||
@@ -1,32 +0,0 @@
|
||||
You can add a new element to the DOM using the `appendChild` or `insertBefore` method.
|
||||
|
||||
## appendChild
|
||||
|
||||
The `appendChild` method adds a new element as the last child of the specified parent element.
|
||||
|
||||
```js
|
||||
const roadmapWrapper = document.querySelector('.roadmap-wrapper');
|
||||
|
||||
const roadmap = document.createElement('div');
|
||||
roadmap.id = 'javascript-roadmap';
|
||||
|
||||
roadmapWrapper.appendChild(roadmapTitle);
|
||||
```
|
||||
|
||||
In the example above, the `roadmap` element is added as the last child of the `roadmapWrapper` element.
|
||||
|
||||
## insertBefore
|
||||
|
||||
The `insertBefore` method adds a new element before the specified child element.
|
||||
|
||||
```js
|
||||
const roadmapWrapper = document.querySelector('.roadmap-wrapper');
|
||||
|
||||
const roadmap = document.createElement('div');
|
||||
roadmap.id = 'javascript-roadmap';
|
||||
|
||||
const roadmapTitle = document.querySelector('#roadmap-title');
|
||||
roadmapWrapper.insertBefore(roadmap, roadmapTitle);
|
||||
```
|
||||
|
||||
In the example above, the `roadmap` element is added before the `roadmapTitle` element.
|
||||
@@ -1,27 +0,0 @@
|
||||
The difference between Asynchronous and Synchronous code is that Asynchronous code does not block the execution of the program while Synchronous code does.
|
||||
|
||||
## Asynchronous code
|
||||
|
||||
Asynchronous code is executed in the background and it does not block the execution of the program. It is usually used to perform tasks that take a long time to complete, such as network requests.
|
||||
|
||||
```js
|
||||
console.log('Before');
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Hello');
|
||||
}, 1000);
|
||||
|
||||
console.log('After');
|
||||
```
|
||||
|
||||
## Synchronous code
|
||||
|
||||
Synchronous code is executed in sequence and it blocks the execution of the program until it is completed. If a task takes a long time to complete, everything else waits.
|
||||
|
||||
```js
|
||||
console.log('Before');
|
||||
|
||||
for (let i = 0; i < 1000000000; i++) {}
|
||||
|
||||
console.log('After');
|
||||
```
|
||||
@@ -1,28 +0,0 @@
|
||||
You can use `break` and `continue` in loops to alter the flow of the loop. `break` will stop the loop from continuing, and `continue` will skip the current iteration and continue the loop.
|
||||
|
||||
```js
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i === 1) {
|
||||
continue; // skips the rest of the code in the loop
|
||||
}
|
||||
console.log(`i: ${i}`);
|
||||
}
|
||||
|
||||
// Output:
|
||||
// i: 0
|
||||
// i: 2
|
||||
// i: 3
|
||||
// i: 4
|
||||
```
|
||||
|
||||
```js
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i === 1) {
|
||||
break; // stops the loop
|
||||
}
|
||||
console.log(`i: ${i}`);
|
||||
}
|
||||
|
||||
// Output:
|
||||
// i: 0
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
**Callback hell**, often referred to as **Pyramid of Doom**, describes a situation in JavaScript where multiple nested callbacks become difficult to manage, leading to unreadable and unmaintainable code. It often arises when performing multiple asynchronous operations that depend on the completion of previous operations. The code starts to take on a pyramidal shape due to the nesting.
|
||||
|
||||
## Example of callback hell
|
||||
|
||||
```js
|
||||
callAsync1(function () {
|
||||
callAsync2(function () {
|
||||
callAsync3(function () {
|
||||
callAsync4(function () {
|
||||
callAsync5(function () {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Strategies to avoid callback hell
|
||||
|
||||
Developers can address or avoid callback hell by using strategies like modularizing the code into named functions, using asynchronous control flow libraries, or leveraging modern JavaScript features like Promises and `async/await` to write more linear, readable asynchronous code.
|
||||
|
||||
### Promise chaining
|
||||
|
||||
```js
|
||||
callAsync1()
|
||||
.then(() => callAsync2())
|
||||
.then(() => callAsync3())
|
||||
.then(() => callAsync4())
|
||||
.then(() => callAsync5())
|
||||
.catch((err) => console.error(err));
|
||||
```
|
||||
|
||||
### Async/await
|
||||
|
||||
```js
|
||||
async function asyncCall() {
|
||||
try {
|
||||
await callAsync1();
|
||||
await callAsync2();
|
||||
await callAsync3();
|
||||
await callAsync4();
|
||||
await callAsync5();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
A closure is a function that has access to its outer function scope even after the outer function has returned. This means a closure can remember and access variables and arguments of its outer function even after the function has finished.
|
||||
|
||||
```js
|
||||
function outer() {
|
||||
const name = 'Roadmap';
|
||||
|
||||
function inner() {
|
||||
console.log(name);
|
||||
}
|
||||
|
||||
return inner;
|
||||
}
|
||||
|
||||
const closure = outer();
|
||||
closure(); // Roadmap
|
||||
```
|
||||
|
||||
In the above example, the `inner` function has access to the `name` variable of the `outer` function even after the `outer` function has returned. Therefore, the `inner` function forms a closure.
|
||||
@@ -1,8 +0,0 @@
|
||||
The Comma Operator `,` evaluates each of its operands (from left to right) and returns the value of the last operand.
|
||||
|
||||
```js
|
||||
let x = 1;
|
||||
x = (x++, x);
|
||||
|
||||
console.log(x); // 2
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
To create a new DOM element, you can use the `document.createElement` method. It accepts a tag name as an argument and returns a new element with the specified tag name. You can set attributes to the element.
|
||||
|
||||
```js
|
||||
const div = document.createElement('div');
|
||||
|
||||
div.id = 'roadmap-wrapper';
|
||||
div.setAttribute('data-id', 'javascript');
|
||||
console.log(div); // <div id="roadmap-wrapper" data-id="javascript"></div>
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
You can use the `CustomEvent` constructor to create a custom event. The `CustomEvent` constructor accepts two arguments: the event name and an optional object that specifies the event options. And you can use the `dispatchEvent` method to dispatch the custom event on the target element/document.
|
||||
|
||||
## Creating Custom Events
|
||||
|
||||
```js
|
||||
const event = new CustomEvent('roadmap-updated', {
|
||||
detail: { name: 'JavaScript' },
|
||||
});
|
||||
element.dispatchEvent(event);
|
||||
```
|
||||
|
||||
## Listening for Custom Events
|
||||
|
||||
You can listen for custom events using the `addEventListener` method. The `addEventListener` method accepts the event name and a callback function that is called when the event is dispatched.
|
||||
|
||||
```js
|
||||
element.addEventListener('roadmap-updated', (event) => {
|
||||
console.log(event.detail); // { name: 'JavaScript' }
|
||||
});
|
||||
```
|
||||
|
||||
## Removing Event Listeners
|
||||
|
||||
You can remove event listeners using the `removeEventListener` method. The `removeEventListener` method accepts the event name and the callback function that was used to add the event listener.
|
||||
|
||||
```js
|
||||
function handleEvent(event) {
|
||||
console.log(event.detail); // { name: 'JavaScript' }
|
||||
}
|
||||
|
||||
element.addEventListener('roadmap-updated', handleEvent);
|
||||
element.removeEventListener('roadmap-updated', handleEvent);
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
Debugging JavaScript code can be achieved through various methods and tools. Here's a basic guide:
|
||||
|
||||
## Console Logging:
|
||||
|
||||
You can use `console.log()`, `console.warn()`, `console.error()`, etc., to print values, variables, or messages to the browser's developer console.
|
||||
|
||||
```js
|
||||
console.log('Value of x:', x);
|
||||
```
|
||||
|
||||
## Browser Developer Tools:
|
||||
|
||||
Most modern browsers come equipped with developer tools. You can access these tools by pressing `F12` or right-clicking on the web page and selecting `Inspect` or `Inspect Element`.
|
||||
|
||||
- **Sources Tab**: Allows you to see the loaded scripts, set breakpoints, and step through the code.
|
||||
- **Console Tab**: Displays console outputs and allows for interactive JavaScript execution.
|
||||
- **Network Tab**: Helps in checking network requests and responses.
|
||||
|
||||
## Setting Breakpoints:
|
||||
|
||||
In the `Sources` tab of the browser's developer tools, you can click on a line number to set a breakpoint. The code execution will pause at this line, allowing you to inspect variables, the call stack, and continue step-by-step.
|
||||
|
||||
## Debugger Statement:
|
||||
|
||||
Inserting the `debugger;` statement in your code will act as a breakpoint when the browser developer tools are open. Execution will pause at the `debugger;` line.
|
||||
|
||||
```js
|
||||
function myFunction() {
|
||||
debugger; // Execution will pause here when dev tools are open
|
||||
// ... rest of the code
|
||||
}
|
||||
```
|
||||
|
||||
## Call Stack and Scope:
|
||||
|
||||
In the developer tools, when paused on a breakpoint or `debugger;` statement, you can inspect the `call stack` to see the sequence of function calls. The `Scope` panel will show you the values of local and global variables.
|
||||
|
||||
Remember, debugging is an iterative process. It often involves setting breakpoints, checking variables, adjusting code, and re-running to ensure correctness.
|
||||
@@ -1,25 +0,0 @@
|
||||
The main difference between `defer` and `async` is the order of execution.
|
||||
|
||||
## Defer attribute
|
||||
|
||||
A `<script>` element with a `defer` attribute, it will continue to load the HTML page and render it while the script is being downloaded. The script is executed after the HTML page has been completely parsed. `defer` scripts maintain their order in the document.
|
||||
|
||||
```html
|
||||
<script defer src="script1.js"></script>
|
||||
<script defer src="script2.js"></script>
|
||||
```
|
||||
|
||||
In the example above, `script1.js` will be executed before `script2.js`. The browser will download both scripts in parallel, but `script1.js` will be executed after the HTML page has been parsed and `script2.js` will be executed after `script1.js` has been executed.
|
||||
|
||||
## Async attribute
|
||||
|
||||
On the other hand, A `<script>` element with an `async` attribute, it will pause the HTML parser and execute the script immediately after it has been downloaded. The HTML parsing will resume after the script has been executed.
|
||||
|
||||
```html
|
||||
<script async src="script1.js"></script>
|
||||
<script async src="script2.js"></script>
|
||||
```
|
||||
|
||||
In the example above, the browser will download both scripts in parallel, and execute them as soon as they are downloaded. The order of execution is not guaranteed.
|
||||
|
||||
To know more you can check [this diagram](https://roadmap.sh/guides/avoid-render-blocking-javascript-with-async-defer) from us that explains the difference between `defer` and `async` in a visual way.
|
||||
@@ -1,14 +0,0 @@
|
||||
The `do...while` statement creates a loop that executes a block of code once, before checking if the condition is `true`, then it will repeat the loop as long as the condition is `true`.
|
||||
|
||||
```js
|
||||
let i = 0;
|
||||
|
||||
do {
|
||||
console.log(i);
|
||||
i++;
|
||||
} while (i < 3);
|
||||
|
||||
// 0
|
||||
// 1
|
||||
// 2
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
The `==` equality operator converts the operands if they are not of the same type, then applies strict comparison. The `===` strict equality operator only considers values equal that have the same type.
|
||||
|
||||
```js
|
||||
console.log(1 == '1'); // true
|
||||
console.log(1 === '1'); // false
|
||||
console.log(1 === 1); // true
|
||||
```
|
||||
@@ -1,24 +0,0 @@
|
||||
In order to handle errors in async/await, we can use the `try/catch` statement.
|
||||
|
||||
## Rejecting a promise
|
||||
|
||||
```js
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
reject(new Error('Something went wrong'));
|
||||
});
|
||||
```
|
||||
|
||||
## Try/catch statement
|
||||
|
||||
```js
|
||||
async function main() {
|
||||
try {
|
||||
const result = await promise;
|
||||
console.log(result);
|
||||
} catch (error) {
|
||||
console.log(error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `catch` block will be executed when the promise is `rejected` or when an error is thrown inside the `try` block.
|
||||
@@ -1,38 +0,0 @@
|
||||
In order to handle errors in promises, we can use the `catch` method or the second argument of the `then` method.
|
||||
|
||||
## Rejecting a promise
|
||||
|
||||
```js
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
reject(new Error('Something went wrong'));
|
||||
});
|
||||
```
|
||||
|
||||
## Catch method
|
||||
|
||||
In this method, we can pass a `callback` function that will be called when the promise is `rejected`.
|
||||
|
||||
```js
|
||||
promise
|
||||
.then((result) => {
|
||||
console.log(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error.message);
|
||||
});
|
||||
```
|
||||
|
||||
## Second argument of the then method
|
||||
|
||||
In this method, we can pass two `callback` functions as arguments. The first one will be called when the promise is `resolved` and the second one will be called when the promise is `rejected`.
|
||||
|
||||
```js
|
||||
promise.then(
|
||||
(result) => {
|
||||
console.log(result);
|
||||
},
|
||||
(error) => {
|
||||
console.log(error.message);
|
||||
}
|
||||
);
|
||||
```
|
||||
@@ -1,19 +0,0 @@
|
||||
Event bubbling is a concept in the Document Object Model (DOM) that describes the way in which events propagate or "bubble up" through the hierarchy of nested elements in the DOM.
|
||||
|
||||
When an event, such as a mouse click, occurs on a DOM element, the event will be handled by the element first, then its parent element, and so on, until the event reaches the root element. This behavior is called event bubbling.
|
||||
|
||||
```js
|
||||
const parent = document.querySelector('.parent');
|
||||
const child = document.querySelector('.child');
|
||||
|
||||
// Scenario of clicking on the child element
|
||||
parent.addEventListener('click', () => {
|
||||
console.log('Handled Last');
|
||||
});
|
||||
|
||||
child.addEventListener('click', () => {
|
||||
console.log('Handled First');
|
||||
});
|
||||
```
|
||||
|
||||
In the above example, when you click on the `child` element, the event will be handled by the `child` element first, then its parent element, and so on, to the root element unless you stop the propagation (`event.stopPropagation()`) of the event.
|
||||
@@ -1,26 +0,0 @@
|
||||
The Event loop has two main components: the Call stack and the Callback queue.
|
||||
|
||||
## Call Stack
|
||||
|
||||
The Call stack is a data structure that stores the tasks that need to be executed. It is a LIFO (Last In, First Out) data structure, which means that the last task that was added to the Call stack will be the first one to be executed.
|
||||
|
||||
## Callback Queue
|
||||
|
||||
The Callback queue is a data structure that stores the tasks that have been completed and are ready to be executed. It is a FIFO (First In, First Out) data structure, which means that the first task that was added to the Callback queue will be the first one to be executed.
|
||||
|
||||
## Event Loop's Workflow:
|
||||
|
||||
1. Executes tasks from the Call Stack.
|
||||
2. For an asynchronous task, such as a timer, it runs in the background. JavaScript proceeds to the next task without waiting.
|
||||
3. When the asynchronous task concludes, its callback function is added to the Callback Queue.
|
||||
4. If the Call Stack is empty and there are tasks in the Callback Queue, the Event Loop transfers the first task from the Queue to the Call Stack for execution.
|
||||
|
||||
```js
|
||||
setTimeout(() => console.log('Hello from the timer'), 0);
|
||||
console.log('Hello from the main code');
|
||||
```
|
||||
|
||||
1. `setTimeout` is processed, and because it's asynchronous, its callback is placed in the Callback Queue.
|
||||
2. The next line, `console.log("Hello from the main code")`, is logged immediately.
|
||||
3. Although the timer duration is 0 milliseconds, its callback has to wait until the Call Stack is empty. After the main code logs, the callback is moved from the Callback Queue to the Call Stack and executed.
|
||||
4. The result is "Hello from the main code" being logged before "Hello from the timer".
|
||||
@@ -1,19 +0,0 @@
|
||||
Explicit binding is a way to explicitly state what the `this` keyword is going to be bound to using `call`, `apply` or `bind` methods of a function.
|
||||
|
||||
```js
|
||||
const roadmap = {
|
||||
name: 'JavaScript',
|
||||
};
|
||||
|
||||
function printName() {
|
||||
console.log(this.name);
|
||||
}
|
||||
|
||||
printName.call(roadmap); // JavaScript
|
||||
printName.apply(roadmap); // JavaScript
|
||||
|
||||
const printRoadmapName = printName.bind(roadmap);
|
||||
printRoadmapName(); // JavaScript
|
||||
```
|
||||
|
||||
In the above example, the `this` keyword inside the `printName()` function is explicitly bound to the `roadmap` object using `call`, `apply` or `bind` methods.
|
||||
@@ -1,12 +0,0 @@
|
||||
You can use the `filter()` method to filter an array based on a condition. The `filter()` method creates a new array with all elements that pass the test implemented by the provided function.
|
||||
|
||||
```js
|
||||
const numbers = [1, 2, 3, 4, 5, 6];
|
||||
|
||||
const evenNumbers = numbers.filter((number) => {
|
||||
return number % 2 === 0;
|
||||
});
|
||||
|
||||
console.log(numbers); // [1, 2, 3, 4, 5, 6]
|
||||
console.log(evenNumbers); // [2, 4, 6]
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
The `finally` block will be executed when the promise is `resolved` or `rejected`.
|
||||
|
||||
```js
|
||||
promise
|
||||
.then((result) => {
|
||||
console.log(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
console.log('Finally Promise has settled');
|
||||
});
|
||||
```
|
||||
@@ -1,55 +0,0 @@
|
||||
There are serveral ways to find unique values in an array. Here are some of them:
|
||||
|
||||
## Using `Set`
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js', 'Node.js', 'JavaScript'];
|
||||
const uniqueRoadmaps = [...new Set(roadmaps)];
|
||||
console.log(uniqueRoadmaps); // ['JavaScript', 'React', 'Node.js']
|
||||
```
|
||||
|
||||
## Using `filter()`
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js', 'Node.js', 'JavaScript'];
|
||||
const uniqueRoadmaps = roadmaps.filter(
|
||||
(roadmap, index) => roadmaps.indexOf(roadmap) === index
|
||||
);
|
||||
console.log(uniqueRoadmaps); // ['JavaScript', 'React', 'Node.js']
|
||||
```
|
||||
|
||||
## Using `reduce()`
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js', 'Node.js', 'JavaScript'];
|
||||
const uniqueRoadmaps = roadmaps.reduce((unique, roadmap) => {
|
||||
return unique.includes(roadmap) ? unique : [...unique, roadmap];
|
||||
}, []);
|
||||
console.log(uniqueRoadmaps); // ['JavaScript', 'React', 'Node.js']
|
||||
```
|
||||
|
||||
## Using `forEach()`
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js', 'Node.js', 'JavaScript'];
|
||||
const uniqueRoadmaps = [];
|
||||
roadmaps.forEach((roadmap) => {
|
||||
if (!uniqueRoadmaps.includes(roadmap)) {
|
||||
uniqueRoadmaps.push(roadmap);
|
||||
}
|
||||
});
|
||||
console.log(uniqueRoadmaps); // ['JavaScript', 'React', 'Node.js']
|
||||
```
|
||||
|
||||
## Using `for...of`
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js', 'Node.js', 'JavaScript'];
|
||||
const uniqueRoadmaps = [];
|
||||
for (const roadmap of roadmaps) {
|
||||
if (!uniqueRoadmaps.includes(roadmap)) {
|
||||
uniqueRoadmaps.push(roadmap);
|
||||
}
|
||||
}
|
||||
console.log(uniqueRoadmaps); // ['JavaScript', 'React', 'Node.js']
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
No, the `forEach()` method does not return a new array. It simply calls a provided function on each element in the array.
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js'];
|
||||
|
||||
roadmaps.forEach((roadmap) => {
|
||||
console.log(roadmap);
|
||||
});
|
||||
```
|
||||
@@ -1,20 +0,0 @@
|
||||
The Head and Stack in JavaScript Engine are two different data structures that store data in different ways.
|
||||
|
||||
## Stack
|
||||
|
||||
The Stack is a small, organized region of memory. It is where primitive values, function calls, and local variables are stored. It follows a "Last In, First Out" (LIFO) order, meaning that the last item added to the stack is the first one to be removed. Each function invocation creates a new stack frame, which contains the function's local variables, return address, and other contextual data.
|
||||
|
||||
## Heap
|
||||
|
||||
The Heap is a large, mostly unstructured region of memory. It is where `objects`, `arrays`, and `functions` are stored. Variables from the Stack (e.g., in functions) point to locations in the Heap for these dynamically allocated structures.
|
||||
|
||||
When you declare a primitive type (like a number or boolean), it's usually managed in the stack. But when you create an object, array, or function, it's stored in the heap, and the stack will hold a reference to that location in the heap.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const name = 'JavaScript'; // Stored on the stack
|
||||
const roadmap = { name: 'JS' }; // `roadmap` reference on the stack, actual object { name: 'JS' } in the heap
|
||||
```
|
||||
|
||||
In the code above, the primitive value `JavaScript` for variable `name` is directly stored on the stack. For the object assigned to `roadmap`, its actual data resides in the heap, and the reference to this data (a memory address pointer) is held on the stack.
|
||||
@@ -1,16 +0,0 @@
|
||||
Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution. This means that no matter where the functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local. Note that hoisting only moves the declaration, not the initialization.
|
||||
|
||||
```js
|
||||
console.log(x === undefined); // true
|
||||
var x = 3;
|
||||
console.log(x); // 3
|
||||
```
|
||||
|
||||
The above code snippet can be visualized in the following way:
|
||||
|
||||
```js
|
||||
var x;
|
||||
console.log(x === undefined); // true
|
||||
x = 3;
|
||||
console.log(x); // 3
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
The IIFE (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it is defined.
|
||||
|
||||
```js
|
||||
(function () {
|
||||
console.log('Hello Roadmap!');
|
||||
})();
|
||||
```
|
||||
|
||||
The IIFE is frequently used to create a new scope to avoid variable hoisting from within blocks.
|
||||
|
||||
```js
|
||||
(function () {
|
||||
var roadmap = 'JavaScript';
|
||||
console.log(roadmap);
|
||||
})();
|
||||
|
||||
console.log(roadmap); // ReferenceError: name is not defined
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
To make an object immutable, you can use `Object.freeze()` method. It prevents the modification of existing property values and prevents the addition of new properties.
|
||||
|
||||
```js
|
||||
const roadmap = {
|
||||
name: 'JavaScript',
|
||||
};
|
||||
|
||||
Object.freeze(roadmap);
|
||||
|
||||
roadmap.name = 'JavaScript Roadmap'; // throws an error in strict mode
|
||||
console.log(roadmap.name); // JavaScript
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
As the name says, the increment operator increases the value of a variable by **1**. There are two types of increment operators: `pre-increment` and `post-increment`.
|
||||
|
||||
## Pre-increment
|
||||
|
||||
The pre-increment operator increases the value of a variable by 1 and then returns the value. For example:
|
||||
|
||||
```js
|
||||
let x = 1;
|
||||
console.log(++x); // 2
|
||||
console.log(x); // 2
|
||||
```
|
||||
|
||||
## Post-increment
|
||||
|
||||
The post-increment operator returns the value of a variable and then increases the value by 1. For example:
|
||||
|
||||
```js
|
||||
let x = 1;
|
||||
console.log(x++); // 1
|
||||
console.log(x); // 2
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
You can use the `while` or `for` loop to create an infinite loop.
|
||||
|
||||
## While loop
|
||||
|
||||
To create an infinite loop with the `while` loop, we can use the `true` keyword as the condition.
|
||||
|
||||
```js
|
||||
while (true) {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
## For loop
|
||||
|
||||
To create an infinite loop with the `for` loop, we can use the `true` keyword as the condition.
|
||||
|
||||
```js
|
||||
for (let i = 0; true; i++) {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
Inheritance is a way to create a new `Class` from an existing `Class`. The new `Class` inherits all the properties and methods from the existing `Class`. The new `Class` is called the child `Class`, and the existing `Class` is called the parent `Class`.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
class Roadmap {
|
||||
constructor(name, description, slug) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.slug = slug;
|
||||
}
|
||||
|
||||
getRoadmapUrl() {
|
||||
console.log(`https://roadmap.sh/${this.slug}`);
|
||||
}
|
||||
}
|
||||
|
||||
class JavaScript extends Roadmap {
|
||||
constructor(name, description, slug) {
|
||||
super(name, description, slug);
|
||||
}
|
||||
|
||||
greet() {
|
||||
console.log(`${this.name} - ${this.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
const js = new JavaScript(
|
||||
'JavaScript Roadmap',
|
||||
'Learn JavaScript',
|
||||
'javascript'
|
||||
);
|
||||
|
||||
js.getRoadmapUrl(); // https://roadmap.sh/javascript
|
||||
js.greet(); // JavaScript Roadmap - Learn JavaScript
|
||||
```
|
||||
|
||||
In the above example, the `JavaScript` class inherits the `getRoadmapUrl()` method from the `Roadmap` class. This is because the `JavaScript` class extends the `Roadmap` class using the `extends` keyword. In the `JavaScript` class, the `getRoadmapUrl()` method is not found, so JavaScript looks up the prototype chain and finds the `getRoadmapUrl()` method in the `Roadmap` class.
|
||||
@@ -1,15 +0,0 @@
|
||||
JavaScript label statements are used to prefix a label to an identifier. It can be used with `break` and `continue` statement to control the flow more precisely.
|
||||
|
||||
```js
|
||||
loop1: for (let i = 0; i < 5; i++) {
|
||||
if (i === 1) {
|
||||
continue loop1; // skips the rest of the code in the loop1
|
||||
}
|
||||
console.log(`i: ${i}`);
|
||||
}
|
||||
// Output:
|
||||
// i: 0
|
||||
// i: 2
|
||||
// i: 3
|
||||
// i: 4
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
There are four logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT), and `??` (Nullish Coalescing). They can be used with boolean values, or with non-boolean values.
|
||||
|
||||
## OR (||)
|
||||
|
||||
The OR operator (`||`) returns the first truthy value, or the last value if none are truthy.
|
||||
|
||||
```js
|
||||
console.log('hello' || 0); // hello
|
||||
console.log(false || 'hello'); // hello
|
||||
console.log('hello' || 'world'); // hello
|
||||
```
|
||||
|
||||
## AND (&&)
|
||||
|
||||
The AND operator (`&&`) aka logical conjunction returns the first falsy value, or the last value if none are falsy.
|
||||
|
||||
```js
|
||||
console.log('hello' && 0); // 0
|
||||
console.log(false && 'hello'); // false
|
||||
console.log('hello' && 'world'); // world
|
||||
```
|
||||
|
||||
## NOT (!)
|
||||
|
||||
It simply inverts the boolean value of its operand.
|
||||
|
||||
```js
|
||||
console.log(!true); // false
|
||||
console.log(!false); // true
|
||||
console.log(!'hello'); // false
|
||||
console.log(!0); // true
|
||||
```
|
||||
|
||||
## Nullish Coalescing (??)
|
||||
|
||||
The Nullish Coalescing Operator (`??`) returns the right operand if the left one is `null` or `undefined`, otherwise, it returns the left operand. It's useful for setting default values without considering falsy values like `0` or `''` as absent.
|
||||
|
||||
```js
|
||||
console.log(null ?? 'hello'); // hello
|
||||
console.log(undefined ?? 'hello'); // hello
|
||||
console.log('' ?? 'hello'); // ''
|
||||
console.log(0 ?? 'hello'); // 0
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
No, the `map()` method does not mutate the original array. It returns a new array with the results of calling a provided function on every element in the calling array.
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js'];
|
||||
|
||||
const renamedRoadmaps = roadmaps.map((roadmap) => {
|
||||
return `${roadmap} Roadmap`;
|
||||
});
|
||||
|
||||
console.log(roadmaps); // ['JavaScript', 'React', 'Node.js']
|
||||
console.log(renamedRoadmaps); // ['JavaScript Roadmap', 'React Roadmap', 'Node.js Roadmap']
|
||||
```
|
||||
@@ -1,19 +0,0 @@
|
||||
Map is another data structure in JavaScript which is similar to `Object` but the key can be of any type. It is a collection of elements where each element is stored as a Key, value pair. It is also known as a Hash table or a dictionary.
|
||||
|
||||
The `key` can be of any type but the `value` can be of any type. The `key` is unique and immutable, whereas the `value` can be mutable or immutable.
|
||||
|
||||
```js
|
||||
const roadmap = new Map();
|
||||
roadmap.set('name', 'JavaScript');
|
||||
roadmap.set('type', 'dynamic');
|
||||
roadmap.set('year', 1995);
|
||||
|
||||
console.log(roadmap.get('name')); // JavaScript
|
||||
|
||||
roadmap.delete('year');
|
||||
console.log(roadmap.has('year')); // false
|
||||
console.log(roadmap.size); // 2
|
||||
|
||||
roadmap.clear();
|
||||
console.log(roadmap.size); // 0
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
You can use `getBoundingClientRect` method to get the dimensions of an element.
|
||||
|
||||
```js
|
||||
const roadmapWrapper = document.querySelector('.roadmap-wrapper');
|
||||
const dimensions = roadmapWrapper.getBoundingClientRect();
|
||||
|
||||
console.log(dimensions); // DOMRect { x: 8, y: 8, width: 784, height: 784, top: 8, right: 792, bottom: 792, left: 8 }
|
||||
```
|
||||
@@ -1,25 +0,0 @@
|
||||
Yes, you can merge multiple arrays into one array using the `concat()` method, or the spread operator `...`.
|
||||
|
||||
## concat()
|
||||
|
||||
The `concat()` method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
|
||||
|
||||
```js
|
||||
const arr1 = [1, 2, 3];
|
||||
const arr2 = [4, 5, 6];
|
||||
|
||||
const arr3 = arr1.concat(arr2);
|
||||
console.log(arr3); // [1, 2, 3, 4, 5, 6]
|
||||
```
|
||||
|
||||
## Spread operator
|
||||
|
||||
The spread operator `...` is used to expand an iterable object into the list of arguments.
|
||||
|
||||
```js
|
||||
const arr1 = [1, 2, 3];
|
||||
const arr2 = [4, 5, 6];
|
||||
|
||||
const arr3 = [...arr1, ...arr2];
|
||||
console.log(arr3); // [1, 2, 3, 4, 5, 6]
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
The Nullish Coalescing Operator (`??`) returns the right operand if the left one is `null` or `undefined`, otherwise, it returns the left operand. It's useful for setting default values without considering falsy values like `0` or `''` as absent.
|
||||
|
||||
```js
|
||||
console.log(null ?? 'hello'); // hello
|
||||
console.log(undefined ?? 'hello'); // hello
|
||||
console.log('' ?? 'hello'); // ''
|
||||
console.log(0 ?? 'hello'); // 0
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
In order to parse JSON, you can use the `JSON.parse()` method. It parses a JSON string and returns the JavaScript equivalent.
|
||||
|
||||
```js
|
||||
const json = '{"name":"JavaScript","year":1995}';
|
||||
const roadmap = JSON.parse(json);
|
||||
|
||||
console.log(roadmap.name); // JavaScript
|
||||
console.log(roadmap.year); // 1995
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
The `event.preventDefault()` method is used to prevent the default action of an event. For example, when you click on a link, the default action is to navigate to the link's URL. But, if you want to prevent the navigation, you can use `event.preventDefault()` method.
|
||||
|
||||
```js
|
||||
const link = document.querySelector('a');
|
||||
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
console.log('Clicked on link!');
|
||||
});
|
||||
```
|
||||
@@ -1,51 +0,0 @@
|
||||
The core difference between `Promise.all()` and `Promise.allSettled()` is that `Promise.all()` rejects immediately if any of the promises reject whereas `Promise.allSettled()` waits for all of the promises to settle (either resolve or reject) and then returns the result.
|
||||
|
||||
## Initialize
|
||||
|
||||
```js
|
||||
const promise1 = Promise.resolve('Promise 1 resolved');
|
||||
const promise2 = Promise.reject('Promise 2 rejected');
|
||||
```
|
||||
|
||||
## Using `Promise.all()`
|
||||
|
||||
```js
|
||||
Promise.all([promise1, promise2])
|
||||
.then((values) => {
|
||||
console.log(values);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('An error occurred in Promise.all():', error);
|
||||
});
|
||||
|
||||
// Output:
|
||||
// An error occurred in Promise.all(): Promise 2 rejected
|
||||
```
|
||||
|
||||
In the above code, the `Promise.all()` rejects immediately when any of the `promise2` rejects.
|
||||
|
||||
## Using `Promise.allSettled()`
|
||||
|
||||
```js
|
||||
Promise.allSettled([promise1, promise2]).then((results) => {
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
console.log(
|
||||
`Promise ${index + 1} was fulfilled with value:`,
|
||||
result.value
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`Promise ${index + 1} was rejected with reason:`,
|
||||
result.reason
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Output:
|
||||
// Promise 1 was fulfilled with value: Promise 1 resolved
|
||||
// Promise 2 was rejected with reason: Promise 2 rejected
|
||||
```
|
||||
|
||||
In the above code, the `Promise.allSettled()` waits for all of the promises to settle (either resolve or reject) and then returns the result.
|
||||
@@ -1,27 +0,0 @@
|
||||
The prototype chain in JavaScript refers to the chain of objects linked by their prototypes. When a property or method is accessed on an object, JavaScript first checks the object itself. If it doesn't find it there, it looks up the property or method in the object's prototype. This process continues, moving up the chain from one prototype to the next, until the property or method is found or the end of the chain is reached (typically the prototype of the base object, which is `null`). The prototype chain is fundamental to JavaScript's prototypal inheritance model, allowing objects to inherit properties and methods from other objects.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const roadmap = {
|
||||
getRoadmapUrl() {
|
||||
console.log(`https://roadmap.sh/${this.slug}`);
|
||||
},
|
||||
};
|
||||
|
||||
const javascript = {
|
||||
name: 'JavaScript Roadmap',
|
||||
description: 'Learn JavaScript',
|
||||
slug: 'javascript',
|
||||
greet() {
|
||||
console.log(`${this.name} - ${this.description}`);
|
||||
},
|
||||
};
|
||||
|
||||
Object.setPrototypeOf(javascript, roadmap); // or javascript.__proto__ = roadmap;
|
||||
|
||||
javascript.getRoadmapUrl(); // https://roadmap.sh/javascript
|
||||
javascript.greet(); // JavaScript Roadmap - Learn JavaScript
|
||||
```
|
||||
|
||||
In the above example, the `javascript` object inherits the `getRoadmapUrl()` method from the `roadmap` object. This is because the `javascript` object's prototype is set to the `roadmap` object using the `Object.setPrototypeOf()` method. In the `javascript` object, the `getRoadmapUrl()` method is not found, so JavaScript looks up the prototype chain and finds the `getRoadmapUrl()` method in the `roadmap` object.
|
||||
@@ -1,18 +0,0 @@
|
||||
For selecting elements in the DOM, the `querySelector` and `querySelectorAll` methods are the most commonly used. They are both methods of the `document` object, and they both accept a CSS selector as an argument.
|
||||
|
||||
## querySelector
|
||||
|
||||
The `querySelector` method returns the first element that matches the specified selector. If no matches are found, it returns `null`.
|
||||
|
||||
```js
|
||||
const roadmapWrapper = document.querySelector('.roadmap-wrapper');
|
||||
const roadmapTitle = document.querySelector('#roadmap-title');
|
||||
```
|
||||
|
||||
## querySelectorAll
|
||||
|
||||
The `querySelectorAll` method returns a `NodeList` of all elements that match the specified selector. If no matches are found, it returns an empty `NodeList`.
|
||||
|
||||
```js
|
||||
const roadmapItems = document.querySelectorAll('.roadmap-item');
|
||||
```
|
||||
@@ -1,24 +0,0 @@
|
||||
You can use the `reduce()` method to reduce an array to a single value. The `reduce()` method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
array.reduce((accumulator, currentValue) => {
|
||||
// ...
|
||||
}, initialValue);
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
You can use the `reduce()` method to sum all the numbers in an array.
|
||||
|
||||
```js
|
||||
const numbers = [1, 2, 3, 4, 5, 6];
|
||||
|
||||
const sum = numbers.reduce((accumulator, currentValue) => {
|
||||
return accumulator + currentValue;
|
||||
}, 0);
|
||||
|
||||
console.log(numbers); // [1, 2, 3, 4, 5, 6]
|
||||
console.log(sum); // 21
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
To remove a DOM element, you can use the `remove` or `removeChild` method of the `Node` interface.
|
||||
|
||||
```js
|
||||
const roadmapWrapper = document.querySelector('.roadmap-wrapper');
|
||||
const roadmapTitle = document.querySelector('#roadmap-title');
|
||||
|
||||
roadmapWrapper.removeChild(roadmapTitle);
|
||||
roadmapWrapper.remove();
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
In order to scroll to the top of the page, we can use the `scrollTo` method.
|
||||
|
||||
```js
|
||||
window.scrollTo(0, 0);
|
||||
```
|
||||
@@ -1,17 +0,0 @@
|
||||
You can run some codes on interval using `setInterval` function in JavaScript. It accepts a function and a time interval in milliseconds. It returns a unique id which you can use to clear the interval using `clearInterval` function.
|
||||
|
||||
```js
|
||||
const intervalId = setInterval(() => {
|
||||
console.log('Hello World');
|
||||
}, 1000);
|
||||
|
||||
// Output:
|
||||
// Hello World
|
||||
// Hello World
|
||||
```
|
||||
|
||||
In the above code, the `setInterval` function runs the callback function every 1000 milliseconds (1 second) and prints `Hello World` to the console. It returns a unique id which you can use to clear the interval using `clearInterval` function.
|
||||
|
||||
```js
|
||||
clearInterval(intervalId);
|
||||
```
|
||||
@@ -1,16 +0,0 @@
|
||||
To run a piece of code after a certain time, you can use `setTimeout` function in JavaScript. It accepts a function and a time interval in milliseconds. It returns a unique id which you can use to clear the timeout using `clearTimeout` function.
|
||||
|
||||
```js
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.log('Hello World');
|
||||
}, 1000);
|
||||
|
||||
// Output:
|
||||
// Hello World
|
||||
```
|
||||
|
||||
In the above code, the `setTimeout` function runs the callback function after 1000 milliseconds (1 second) and prints `Hello World` to the console. It returns a unique id which you can use to clear the timeout using `clearTimeout` function.
|
||||
|
||||
```js
|
||||
clearTimeout(timeoutId);
|
||||
```
|
||||
@@ -1,17 +0,0 @@
|
||||
Set is another data structure in JavaScript which is similar to `Array` but the values are unique. It is a collection of elements where each element is stored as a value without any keys.
|
||||
|
||||
```js
|
||||
const roadmap = new Set();
|
||||
roadmap.add('JavaScript');
|
||||
roadmap.add('JavaScript');
|
||||
|
||||
roadmap.add('dynamic');
|
||||
roadmap.add(1995);
|
||||
|
||||
console.log(roadmap.size); // 3, because the value 'JavaScript' is already present in the set
|
||||
console.log(roadmap.has('JavaScript')); // true
|
||||
|
||||
roadmap.delete('JavaScript');
|
||||
console.log(roadmap.has('JavaScript')); // false
|
||||
console.log(roadmap.size); // 2
|
||||
```
|
||||
@@ -1,19 +0,0 @@
|
||||
The spread operator in JavaScript is represented by three dots (`...`). It allows the elements of an array or properties of an object to be expanded or "spread" into individual elements or properties. This can be useful in various contexts, such as when passing elements as function arguments, cloning arrays and objects, or merging arrays and objects.
|
||||
|
||||
```js
|
||||
const roadmaps = ['JavaScript', 'React', 'Node.js'];
|
||||
const bestPractices = ['AWS', 'API Security'];
|
||||
|
||||
const resources = [...roadmaps, ...bestPractices];
|
||||
console.log(resources); // ['JavaScript', 'React', 'Node.js', 'AWS', 'API Security']
|
||||
```
|
||||
|
||||
```js
|
||||
const roadmap = {
|
||||
name: 'JavaScript',
|
||||
type: 'dynamic',
|
||||
};
|
||||
|
||||
const roadmapClone = { ...roadmap }; // shallow copy
|
||||
console.log(roadmapClone); // { name: 'JavaScript', type: 'dynamic' }
|
||||
```
|
||||
@@ -1,19 +0,0 @@
|
||||
The `switch` statement evaluates an expression, matching the expression's value to a `case` clause, and executes statements associated with that `case`, as well as statements in `case`s that follow the matching `case`.
|
||||
|
||||
```js
|
||||
const fruit = 'Papayas';
|
||||
|
||||
switch (fruit) {
|
||||
case 'Oranges':
|
||||
console.log('Oranges are $0.59 a pound.');
|
||||
break;
|
||||
case 'Mangoes':
|
||||
case 'Papayas':
|
||||
console.log('Mangoes and papayas are $2.79 a pound.');
|
||||
break;
|
||||
default:
|
||||
console.log(`Sorry, we are out of ${fruit}.`);
|
||||
}
|
||||
|
||||
// Mangoes and papayas are $2.79 a pound.
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
The ternary operator is a conditional operator that takes three operands. It is frequently used as a shortcut for the `if` statement.
|
||||
|
||||
```js
|
||||
console.log(condition ? true : false);
|
||||
```
|
||||
@@ -1,27 +0,0 @@
|
||||
In JavaScript, you can accept a variable number of arguments in a function using the `arguments` object or the rest parameter (`...`).
|
||||
|
||||
## Using the `arguments` object:
|
||||
|
||||
The `arguments` is an array-like object that holds all of the passed arguments. They are only available inside the function body.
|
||||
|
||||
```js
|
||||
function displayArgs() {
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
console.log(arguments[i]);
|
||||
}
|
||||
}
|
||||
displayArgs(1, 2, 3, 4); // Outputs: 1, 2, 3, 4
|
||||
```
|
||||
|
||||
## Using the rest parameter:
|
||||
|
||||
The rest parameter allows you to represent an indefinite number of arguments as an array.
|
||||
|
||||
```js
|
||||
function displayArgs(...args) {
|
||||
args.forEach((arg) => console.log(arg));
|
||||
}
|
||||
displayArgs(1, 2, 3, 4); // Outputs: 1, 2, 3, 4
|
||||
```
|
||||
|
||||
The rest parameter (`...args` in the example) is generally more modern and flexible, and it provides an actual array, unlike the array-like `arguments` object.
|
||||
@@ -1,396 +0,0 @@
|
||||
---
|
||||
order: 1
|
||||
briefTitle: 'JavaScript'
|
||||
briefDescription: 'Test, rate and improve your JavaScript knowledge with these questions.'
|
||||
title: 'JavaScript Questions'
|
||||
description: 'Test, rate and improve your JavaScript knowledge with these questions.'
|
||||
isNew: true
|
||||
seo:
|
||||
title: 'JavaScript Questions'
|
||||
description: 'Curated list of JavaScript questions to test, rate and improve your knowledge. Questions are based on real world experience and knowledge.'
|
||||
keywords:
|
||||
- 'javascript quiz'
|
||||
- 'javascript questions'
|
||||
- 'javascript interview questions'
|
||||
- 'javascript interview'
|
||||
- 'javascript test'
|
||||
sitemap:
|
||||
priority: 1
|
||||
changefreq: 'monthly'
|
||||
questions:
|
||||
- question: What is JavaScript?
|
||||
answer: JavaScript (often abbreviated as JS) is a high-level, versatile, and widely-used programming language primarily known for its role in web development. It enables interactive and dynamic behavior on websites.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What is the difference between `var`, `let`, and `const` in JavaScript?
|
||||
answer: In JavaScript, `var` is function-scoped and was traditionally used to declare variables. `let` and `const` are block-scoped. The key difference between `let` and `const` is that `let` allows for reassignment while `const` creates a read-only reference.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What is the difference between `null` and `undefined`?
|
||||
answer: The `null` is an assignment value. It can be assigned to a variable as a representation of no value. But the `undefined` is a primitive value that represents the absence of a value, or a variable that has not been assigned a value.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What is the difference between `==` and `===`?
|
||||
answer: equality-operator.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What are the different ways to declare a variable in JavaScript?
|
||||
answer: There are three ways to declare a variable in JavaScript `var`, `let`, and `const`.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What are Scopes in JavaScript?
|
||||
answer: A scope is a set of variables, objects, and functions that you have access to. There are three types of scopes in JavaScript. Which are Global Scope, Function Scope (Local Scope), and Block Scope.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What is ternary operator in JavaScript?
|
||||
answer: ternary-operator.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: How to implement your own Custom Event in JavaScript?
|
||||
answer: custom-event.md
|
||||
topics:
|
||||
- 'Event'
|
||||
- 'Advanced'
|
||||
- question: What is a closure in JavaScript?
|
||||
answer: closure.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Advanced'
|
||||
- question: Does Arrow functions have their own `this`?
|
||||
answer: No, arrow functions do not have their own `this`. Instead, they inherit the `this` of the enclosing lexical scope.
|
||||
topics:
|
||||
- 'Function'
|
||||
- 'Intermediate'
|
||||
- question: Does `map()` method mutate the original array?
|
||||
answer: map-method.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: Does `forEach()` method return a new array?
|
||||
answer: for-each-method.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: How to use `filter()` method?
|
||||
answer: filter-method.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What is the difference between `map()` and `forEach()` methods?
|
||||
answer: The `map()` method creates a new array with the results of calling a provided function on every element in the calling array. Whereas, the `forEach()` method executes a provided function once for each array element.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: How to use `reduce()` method?
|
||||
answer: reduce-method.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What is the difference between `map()` and `reduce()` methods?
|
||||
answer: The `map()` method creates a new array with the results of calling a provided function on every element in the calling array. Whereas, the `reduce()` method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What is Prototype Chain in JavaScript?
|
||||
answer: prototype-chain.md
|
||||
topics:
|
||||
- 'OOP'
|
||||
- 'Advanced'
|
||||
- question: What is IIFE in JavaScript?
|
||||
answer: iife.md
|
||||
topics:
|
||||
- 'Function'
|
||||
- 'Advanced'
|
||||
- question: What is Inheritance in JavaScript?
|
||||
answer: inheritance.md
|
||||
topics:
|
||||
- 'OOP'
|
||||
- 'Advanced'
|
||||
- question: What is Map in JavaScript?
|
||||
answer: map.md
|
||||
topics:
|
||||
- 'Date Type'
|
||||
- 'Beginner'
|
||||
- question: What is Set in JavaScript?
|
||||
answer: set.md
|
||||
topics:
|
||||
- 'Data Type'
|
||||
- 'Beginner'
|
||||
- question: How you can find unique values in an array?
|
||||
answer: find-unique-array-values.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What is a JavaScript promise?
|
||||
answer: A Promise in JavaScript represents a value that may not be available yet but will be at some point. Promises provide a way to handle asynchronous operations, offering methods like `.then()` and `.catch()` to register callbacks for success and failure.
|
||||
topics:
|
||||
- 'Promise'
|
||||
- 'Advanced'
|
||||
- question: What is the purpose of the `async/await` in JavaScript?
|
||||
answer: The `async/await`, introduced in ES2017, provides a more readable and cleaner way to handle asynchronous operations compared to callbacks and promises. An `async` function always returns a promise, and within such a function, you can use `await` to pause execution until a promise settles.
|
||||
topics:
|
||||
- 'Promise'
|
||||
- 'Advanced'
|
||||
- question: What is callback hell in JavaScript?
|
||||
answer: callback-hell.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Advanced'
|
||||
- question: How to enable strict mode in JavaScript?
|
||||
answer: To enable strict mode in JavaScript, you need to add the following line at the top of the file or function `'use strict';`.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: Explain `alert()`, `prompt()`, and `confirm()` methods in JavaScript?
|
||||
answer: alert-prompt-confirm.md
|
||||
topics:
|
||||
- 'Event'
|
||||
- 'Intermediate'
|
||||
- question: How to handle event bubbling in JavaScript?
|
||||
answer: event-bubbling.md
|
||||
topics:
|
||||
- 'Event'
|
||||
- 'Beginner'
|
||||
- question: What is Event Capturing in JavaScript?
|
||||
answer: Event capturing is the first phase of event propagation. In this phase, the event is captured by the outermost element and propagated to the inner elements. It is also known as trickling. It is the opposite of event bubbling.
|
||||
topics:
|
||||
- 'Event'
|
||||
- 'Beginner'
|
||||
- question: What is the spread operator in JavaScript?
|
||||
answer: spread-operator.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: Is Java and JavaScript the same?
|
||||
answer: No, Java and JavaScript are distinct languages. Their similarity in name is coincidental, much like `car` and `carpet`. Java is often used for backend and mobile apps, while JavaScript powers web interactivity and backend.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What is `preventDefault()` method in JavaScript?
|
||||
answer: prevent-default.md
|
||||
topics:
|
||||
- 'Event'
|
||||
- 'Intermediate'
|
||||
- question: What is Hoisting in JavaScript?
|
||||
answer: hoisting.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What is DOM?
|
||||
answer: The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as nodes and objects.
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
- question: Difference between `Promise.all()` and `Promise.allSettled()`?
|
||||
answer: promise-all-vs-all-settled.md
|
||||
topics:
|
||||
- 'Promise'
|
||||
- 'Advanced'
|
||||
- question: What is the difference between `Map` and `WeakMap` in JavaScript?
|
||||
answer: The `Map` object holds key-value pairs and remembers the original insertion order of the keys. Whereas, the `WeakMap` object is a collection of key/value pairs in which the keys are weakly referenced. You can use any data type as a key or value in a `Map` whereas in `WeakMap` you can only use objects as keys. The `WeakMap` is not iterable whereas `Map` is. In `WeakMap` it holds the weak reference to the original object which means if there are no other references to an object stored in the `WeakMap`, those objects can be garbage collected.
|
||||
topics:
|
||||
- 'Data Type'
|
||||
- 'Advanced'
|
||||
- question: Garbage collection in JavaScript?
|
||||
answer: The JavaScript engine uses automatic garbage collection. JavaScript automatically manages memory by freeing up space used by objects no longer needed. This algorithm is called Mark and Sweep, which is performed periodically by the JavaScript engine.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Advanced'
|
||||
- question: How to make an Object immutable in JavaScript?
|
||||
answer: immutable-object.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Advanced'
|
||||
- question: What is Type Casting?
|
||||
answer: Type conversion (or typecasting) means transfer of data from one data type to another. Implicit conversion happens when the compiler (for compiled languages) or runtime (for script languages like `JavaScript`) automatically converts data types.
|
||||
topics:
|
||||
- 'Data Type'
|
||||
- 'Intermediate'
|
||||
- question: What are Explicit binding in JavaScript?
|
||||
answer: explicit-binding.md
|
||||
topics:
|
||||
- 'Function'
|
||||
- 'Advanced'
|
||||
- question: How to run a piece of code after a specific time interval?
|
||||
answer: set-interval.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: How to run a piece of code only once after a specific time?
|
||||
answer: set-timeout.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What are Labelled Statements in JavaScript?
|
||||
answer: labelled-statements.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: Difference between `defer` and `async` attributes in JavaScript?
|
||||
answer: defer-vs-async.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What is Increment operator in JavaScript?
|
||||
answer: increment-operator.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: How to accept variable number of arguments in a JavaScript function?
|
||||
answer: variable-number-of-arguments.md
|
||||
topics:
|
||||
- 'Function'
|
||||
- 'Intermediate'
|
||||
- question: How to define multiline strings in JavaScript?
|
||||
answer: In order to define multiline strings in JavaScript, you need to use template literals. Template literals are enclosed by the backtick (```` ` ` ````) character instead of double or single quotes. Template literals can contain placeholders. These are indicated by the dollar sign and curly braces (``` `${expression}` ```).
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: Uses of `break` and `continue` statements in JavaScript?
|
||||
answer: break-and-continue.md
|
||||
topics:
|
||||
- 'Loop'
|
||||
- 'Beginner'
|
||||
- question: How to parse JSON in JavaScript?
|
||||
answer: parse-json.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: How to debug JavaScript code?
|
||||
answer: debug-javascript.md
|
||||
topics:
|
||||
- 'Debug'
|
||||
- 'Beginner'
|
||||
- question: How to handle error in Promise?
|
||||
answer: error-in-promise.md
|
||||
topics:
|
||||
- 'Promise'
|
||||
- 'Advanced'
|
||||
- question: How to handle error in async/await?
|
||||
answer: error-in-async-await.md
|
||||
topics:
|
||||
- 'Promise'
|
||||
- 'Advanced'
|
||||
- question: How to use `finally` block in Promise?
|
||||
answer: finally-block-in-promise.md
|
||||
topics:
|
||||
- 'Promise'
|
||||
- 'Advanced'
|
||||
- question: Asynchronous vs Synchronous code?
|
||||
answer: async-vs-sync.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: What is Event Loop in JavaScript?
|
||||
answer: The Event loop is one the most important aspect to understand in JavaScript. It is the mechanism that allows JavaScript to perform non-blocking operations. It is the reason why we can use asynchronous code in JavaScript. The Event loop is a loop that constantly checks if there are any tasks that need to be executed. If there are, it will execute them. If there are no tasks to execute, it will wait for new tasks to arrive.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Advanced'
|
||||
- question: How does Event Loop work in JavaScript?
|
||||
answer: event-loop.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Advanced'
|
||||
- question: Is it possible to run JavaScript outside the browser?
|
||||
answer: Yes, it is possible to run JavaScript outside the browser. There are several ways to run JavaScript outside the browser. You can use **Node.js**, **Deno**, **Bun**, or any other JavaScript runtime environment.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: Is it possible to run 2 lines of code at the same time in JavaScript?
|
||||
answer: No, it is not possible to run 2 lines of code at the same time in JavaScript. JavaScript is a single-threaded language, which means that it can only execute one line of code at a time. However, it is possible to run 2 lines of code at the same time using asynchronous code.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: Is JavaScript a compiled or interpreted language?
|
||||
answer: JavaScript is an interpreted language. This means that the JavaScript code is not compiled before it is executed. Instead, the JavaScript engine interprets the code at runtime.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: Are references copied in JavaScript?
|
||||
answer: No, references are not copied in JavaScript. When you assign an object to a variable, the variable will contain a reference to the object. If you assign the variable to another variable, the second variable will also contain a reference to the object. If you change the object using one of the variables, the change will be visible using the other variable.
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: What are Heap and Stack in JavaScript?
|
||||
answer: heap-and-stack.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Advanced'
|
||||
- question: What is Comma Operator in JavaScript?
|
||||
answer: comma-operator.md
|
||||
topics:
|
||||
- 'Operator'
|
||||
- 'Intermediate'
|
||||
- question: What is Nullish Coalescing Operator?
|
||||
answer: nullish-coalescing-operator.md
|
||||
topics:
|
||||
- 'Operator'
|
||||
- 'Beginner'
|
||||
- question: What are the Logical Operators in JavaScript?
|
||||
answer: logical-operators.md
|
||||
topics:
|
||||
- 'Operator'
|
||||
- 'Beginner'
|
||||
- question: How to create Infinite Loop in JavaScript?
|
||||
answer: infinite-loop.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: How to use `do...while` loop in JavaScript?
|
||||
answer: do-while-loop.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: Switch case statement in JavaScript?
|
||||
answer: switch-case.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Beginner'
|
||||
- question: How to select DOM elements using `querySelector()` and `querySelectorAll()`?
|
||||
answer: query-selector.md
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
- question: How to create a new Element in DOM?
|
||||
answer: create-element.md
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
- question: Difference between `appendChild()` and `insertBefore()`?
|
||||
answer: append-child-vs-insert-before.md
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
- question: How to remove an Element from DOM?
|
||||
answer: remove-element.md
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
- question: How to scroll to the top of the page using JavaScript?
|
||||
answer: scroll-to-top.md
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
- question: How to measure dimensions of an Element?
|
||||
answer: measure-dimensions.md
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
- question: Can you merge multiple arrays in JavaScript?
|
||||
answer: merge-arrays.md
|
||||
topics:
|
||||
- 'Core'
|
||||
- 'Intermediate'
|
||||
- question: How to get viewport dimensions in JavaScript?
|
||||
answer: You can use `window.innerWidth` and `window.innerHeight` to get the viewport dimensions.
|
||||
topics:
|
||||
- 'DOM'
|
||||
- 'Beginner'
|
||||
---
|
||||
@@ -1,32 +0,0 @@
|
||||
In React functional components, lifecycle-like behaviors are achieved using hooks:
|
||||
|
||||
## Mounting and Unmounting
|
||||
|
||||
Utilizing the useEffect hook with an empty dependency array ([]) ensures the hook runs after the component mounts to the DOM.
|
||||
|
||||
```js
|
||||
useEffect(() => {
|
||||
// do something after component mounts
|
||||
return () => {
|
||||
// do something before component unmounts
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
The cleanup function returned within the useEffect callback offers a mechanism for handling tasks when the component is about to **unmount**.
|
||||
|
||||
## Updates
|
||||
|
||||
The useEffect hook, when invoked without a dependency array or with specific dependencies, executes after every render or when specified prop/state changes are detected.
|
||||
|
||||
```js
|
||||
useEffect(() => {
|
||||
// do something after every render
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
useEffect(() => {
|
||||
// do something after specific prop/state changes
|
||||
}, [state1, state2]);
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
A Controlled Component is one that takes its current value through props and notifies changes through callbacks like `onChange`. A parent component "controls" it by handling the callback and managing its own state and passing the new values as props to the controlled component. You could also call this a "dumb component".
|
||||
|
||||
A Uncontrolled Component is one that stores its own state internally, and you query the DOM using a `ref` to find its current value when you need it. This is a bit more like traditional HTML.
|
||||
|
||||
Most native React form components support both controlled and uncontrolled usage:
|
||||
|
||||
```jsx
|
||||
// Controlled:
|
||||
<input type="text" value={value} onChange={handleChange} />
|
||||
|
||||
// Uncontrolled:
|
||||
<input type="text" defaultValue="foo" ref={inputRef} />
|
||||
// Use `inputRef.current.value` to read the current value of <input>
|
||||
```
|
||||
@@ -1,60 +0,0 @@
|
||||
`createPortal` is a method on the `ReactDOM` object. It is used to render a React element into another DOM element outside of the parent component. This is useful for cases like modals, popups, or tooltips where you want the component to break out of its container.
|
||||
|
||||
```js
|
||||
ReactDOM.createPortal(child, container);
|
||||
```
|
||||
|
||||
The first argument (`child`) is any renderable React child, such as an element, string, or fragment. The second argument (`container`) is a DOM element.
|
||||
|
||||
The `Modal` component below is a simple example of a modal component that uses `createPortal` to render its children into a DOM element with the id `root`. The `Modal` component is rendered as a child of the `App` component, but the modal itself is rendered outside of the `App` component.
|
||||
|
||||
```js
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
export function Modal({ isOpen, onClose, children }) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
backgroundColor: 'white',
|
||||
padding: '20px',
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<button onClick={onClose} aria-label="Close Modal">
|
||||
Close
|
||||
</button>
|
||||
{children}
|
||||
</div>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The `Modal` component can be used like this:
|
||||
|
||||
```js
|
||||
import { useState } from 'react';
|
||||
import { Modal } from './modal';
|
||||
|
||||
export function App() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setIsOpen(true)}>Open Modal</button>
|
||||
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
||||
<h1>Modal Title</h1>
|
||||
<p>Modal Content</p>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -1,51 +0,0 @@
|
||||
**Custom hooks** are a mechanism for code reuse in React and allow you to extract component logic into reusable functions. Custom hooks can be used to share logic between components or to abstract away complex logic to make components more readable.
|
||||
|
||||
Let's look at an example of a custom hook that return network status information:
|
||||
|
||||
## Creating a Custom hook
|
||||
|
||||
Custom hooks are named with the prefix `use` and can call other hooks if needed. They can also accept arguments and return values.
|
||||
|
||||
```js
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function useNetworkStatus() {
|
||||
const [isOnline, setIsOnline] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
function handleOnline() {
|
||||
setIsOnline(true);
|
||||
}
|
||||
|
||||
function handleOffline() {
|
||||
setIsOnline(false);
|
||||
}
|
||||
|
||||
window.addEventListener('online', handleOnline);
|
||||
window.addEventListener('offline', handleOffline);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', handleOnline);
|
||||
window.removeEventListener('offline', handleOffline);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isOnline;
|
||||
}
|
||||
```
|
||||
|
||||
The custom hook above uses the `useState` and `useEffect` hooks to track the network status of the browser. It returns a boolean value that indicates whether the browser is online or offline.
|
||||
|
||||
## Using a Custom hook
|
||||
|
||||
```js
|
||||
function NetworkStatus() {
|
||||
const isOnline = useNetworkStatus();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>You are {isOnline ? 'online' : 'offline'}.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -1,37 +0,0 @@
|
||||
Error boundaries are special React components that catch JavaScript errors during rendering, in lifecycle methods, and during the constructor of whole tree below them. They are used to handle errors gracefully by displaying a fallback UI and preventing the entire application from crashing due to unhandled errors.
|
||||
|
||||
You can use [react-error-boundary](https://npm.im/react-error-boundary) package to create error boundaries in your application. It provides a `ErrorBoundary` component that you can wrap around any component that might throw an error. The `ErrorBoundary` component takes a `FallbackComponent` prop that is used to render a fallback UI when an error occurs.
|
||||
|
||||
## Capturing Errors
|
||||
|
||||
```js
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { FetchData } from './FetchData';
|
||||
|
||||
function ErrorFallback({ error, resetErrorBoundary }) {
|
||||
return (
|
||||
<div role="alert">
|
||||
<p>Something went wrong:</p>
|
||||
<pre>{error.message}</pre>
|
||||
<button onClick={resetErrorBoundary}>Try again</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<FetchData />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
This `FetchData` component will throw an error when it is rendered, and the `ErrorBoundary` component will catch the error and display the `ErrorFallback` component.
|
||||
|
||||
```js
|
||||
export function FetchData() {
|
||||
throw new Error('Error fetching data');
|
||||
return <p>This will never render</p>;
|
||||
}
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
The `flushSync` function in React is used to flush updates synchronously. It schedules updates to be performed inside a high-priority task, ensuring that the updates are executed immediately and synchronously before returning control to the caller.
|
||||
|
||||
```js
|
||||
import { flushSync } from 'react-dom';
|
||||
|
||||
flushSync(callback);
|
||||
```
|
||||
|
||||
This is useful in situations where you need the DOM to be updated immediately, such as for measurements or to ensure synchronous rendering. However, excessive use of `flushSync` can lead to degraded performance, so it should be used judiciously.
|
||||
@@ -1,33 +0,0 @@
|
||||
There are many reasons why an app might be slow. It could be due to a slow network, a slow backend, or a slow client. It could also be due to a memory leak, unnecessary re-renders, or large bundle sizes.
|
||||
|
||||
Here are some tips to help you investigate and fix performance issues:
|
||||
|
||||
## Use the React DevTools Profiler
|
||||
|
||||
The React DevTools Profiler helps you visualize how components render and identify costly renderings. It can also help you identify unnecessary re-renders.
|
||||
|
||||
## Check for Unnecessary Renders
|
||||
|
||||
Ensure that components don't render more often than needed. Be clear about the `useEffect` dependencies and avoid creating new objects or arrays every render, as these can trigger unnecessary child component renders. Tools like [why-did-you-render](https://npm.im/@welldone-software/why-did-you-render) can help spot unnecessary re-renders.
|
||||
|
||||
## Analyze Bundle Size
|
||||
|
||||
Use your production build to analyze your bundle size. Tools like [webpack-bundle-analyzer](https://npm.im/webpack-bundle-analyzer) or [source-map-explorer](https://npm.im/source-map-explorer) can help you see if large libraries or unused code is slowing down the initial load.
|
||||
|
||||
## Optimize Images & Assets
|
||||
|
||||
Ensure images are appropriately sized and use modern formats. Also, consider using CDNs for assets that don't change often.
|
||||
|
||||
## Lazy Load Components
|
||||
|
||||
Use `lazy()` and dynamic imports to split your bundle and load components only when they're needed. This can help reduce the initial load time.
|
||||
|
||||
## Check Network Requests
|
||||
|
||||
Slow API calls or fetching large amounts of data can affect performance. Optimize your backend, paginate data, or cache results. You can also use tools like [@tanstack/react-query](https://npm.im/@tanstack/react-query) or [swr](https://npm.im/swr) to help manage data fetching and caching.
|
||||
|
||||
## Use Production Build for Testing
|
||||
|
||||
Ensure you're testing the performance on a production build, as development builds are often slower due to extra checks and logs.
|
||||
|
||||
Regularly profiling and monitoring your app can help you spot and fix performance issues before they become significant problems. You can use tools like [Lighthouse](https://developers.google.com/web/tools/lighthouse) or [Calibre](https://calibreapp.com) to monitor your app's performance over time.
|
||||
@@ -1,39 +0,0 @@
|
||||
You can use React's `lazy()` function in conjunction with dynamic `import()` to lazily load a component. This is often combined with `Suspense` to display fallback content while the component is being loaded.
|
||||
|
||||
```js
|
||||
// The component has to be exported as a default export
|
||||
export default function RoadmapRender() {
|
||||
return <h1>This is a lazily-loaded component!</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const LazyRoadmapRender = lazy(() => delay(import('./RoadmapRender')));
|
||||
|
||||
export function App() {
|
||||
const [showRoadmapRender, setShowRoadmapRender] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setShowRoadmapRender(true)}>
|
||||
Show RoadmapRender
|
||||
</button>
|
||||
{showRoadmapRender && (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<LazyRoadmapRender />
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to simulate a 2 seconds delay
|
||||
function delay(promise) {
|
||||
return new Promise((resolve) => setTimeout(resolve, 2000)).then(
|
||||
() => promise
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The `RoadmapRender` component is lazily loaded and rendered inside the `Suspense` component. While the component is being loaded, the `Suspense` component will display the fallback content.
|
||||
@@ -1,27 +0,0 @@
|
||||
Pure components re-render only when the props passed to the component changes. For example, if you have a pure child component inside a parent component state changes in the parent component will not re-render the child component unless the props passed to the child component change.
|
||||
|
||||
To create a pure component, you can use the `memo` function from React. It is a higher order component which takes a component as an argument and returns a new component. The new component renders only if the props change.
|
||||
|
||||
```javascript
|
||||
import React, { memo } from 'react';
|
||||
|
||||
const ChildComponent = ({ name }) => {
|
||||
console.log('Rendering child component');
|
||||
return <div>{name}</div>;
|
||||
};
|
||||
|
||||
const PureChildComponent = memo(ChildComponent);
|
||||
|
||||
const ParentComponent = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
const [name, setName] = useState('John');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setCount(count + 1)}>Count - {count}</button>
|
||||
<button onClick={() => setName('Jane')}>Change name</button>
|
||||
<PureChildComponent name={name} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
Unnecessary re-renders in components can occur due to several reasons, and it's important to optimize your code to minimize them for better performance.
|
||||
|
||||
Here are some common reasons for unnecessary re-renders in functional components:
|
||||
|
||||
- **Using inline functions in JSX props**: If you pass an inline function as a prop to child components, those components will get re-rendered every time the parent component re-renders. This is because a new function is created on every render. You can optimize this by using `useCallback` hook to memoize the function.
|
||||
- **Using `useState` hook with objects**: If you use `useState` hook with objects, you need to make sure that you are not mutating the object. If you mutate the object, React will not be able to detect the change and will not re-render the component. You can optimize this by using `useReducer` hook instead of `useState` hook.
|
||||
- **Using `useEffect` hook without dependencies**: If you use `useEffect` hook without dependencies, it will run on every render. You can optimize this by passing an empty array as the second argument to `useEffect` hook.
|
||||
- **Parent Component Re-renders**: If a parent component re-renders, all its child components will also re-render. You can optimize this by using `React.memo` to memoize the child component where possible.
|
||||
- **Global State Changes**: If you use global state management libraries like Redux, MobX, etc., and the global state changes, all the components that use that state will re-render. You can optimize this by using `useSelector` hook to select only the state that you need in a component.
|
||||
- **Misusing Context**: If you use Context API to pass data to child components, and the data changes, all the child components will re-render. You can optimize this by using `useContext` hook to select only the data that you need in a component.
|
||||
|
||||
You can also use `React.StrictMode` to detect potential problems in your code that could cause unnecessary re-renders.
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
By default, each component’s DOM nodes are private. However, sometimes it’s useful to expose a DOM node to the parent—for example, to allow focusing it. To opt in, wrap your component definition into `forwardRef()`:
|
||||
|
||||
```javascript
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
const MyInput = forwardRef(function MyInput(props, ref) {
|
||||
const { label, ...otherProps } = props;
|
||||
return (
|
||||
<label>
|
||||
{label}
|
||||
<input {...otherProps} />
|
||||
</label>
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
You will receive a `ref` as the second argument after props. Pass it to the DOM node that you want to expose:
|
||||
|
||||
```javascript
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
const MyInput = forwardRef(function MyInput(props, ref) {
|
||||
const { label, ...otherProps } = props;
|
||||
return (
|
||||
<label>
|
||||
{label}
|
||||
<input {...otherProps} ref={ref} />
|
||||
</label>
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
This lets the parent Form component access their `<input>` DOM node exposed by `MyInput`:
|
||||
|
||||
```javascript
|
||||
function Form() {
|
||||
const ref = useRef(null);
|
||||
|
||||
function handleClick() {
|
||||
ref.current.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
<MyInput label="Enter your name:" ref={ref} />
|
||||
<button type="button" onClick={handleClick}>
|
||||
Edit
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
This Form component passes a ref to `MyInput`. The `MyInput` component forwards that ref to the `<input>` browser tag. As a result, the `Form` component can access that `<input>` DOM node and call `focus()` on it.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user