├── .DS_Store
├── .github
└── .images
│ ├── dark-theme.jpg
│ ├── formik-todo.jpg
│ ├── light-theme.jpg
│ ├── randomuserapi-table.jpg
│ └── spotlight-search.jpg
├── 1. spotlight-search
├── .gitignore
├── README.md
├── craco.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.tsx
│ ├── assets
│ │ └── images
│ │ │ └── background.jpg
│ ├── components
│ │ ├── InputField.tsx
│ │ └── Suggestions.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ ├── reportWebVitals.ts
│ └── resources
│ │ └── words.ts
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
├── 2. formik-todo
├── .gitignore
├── README.md
├── craco.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.tsx
│ ├── components
│ │ ├── AddTodoBar.tsx
│ │ └── TodoItem.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ ├── reportWebVitals.ts
│ └── setupTests.ts
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
├── 3. dark-theme-toggle
├── .gitignore
├── README.md
├── craco.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.tsx
│ ├── assets
│ │ ├── icons
│ │ │ ├── apple.svg
│ │ │ └── battery.svg
│ │ └── images
│ │ │ ├── batman.jpg
│ │ │ ├── interstellar.jpg
│ │ │ └── loki.jpg
│ ├── components
│ │ ├── Navbar
│ │ │ └── Navbar.tsx
│ │ └── SystemPreferences
│ │ │ ├── Frame
│ │ │ └── Frame.tsx
│ │ │ ├── SideBar
│ │ │ └── SideBar.tsx
│ │ │ └── SystemPreferences.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ ├── reportWebVitals.ts
│ └── setupTests.ts
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
├── 4. randomuser-api-table
├── .gitignore
├── README.md
├── craco.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.tsx
│ ├── components
│ │ ├── ExportDropdown.tsx
│ │ ├── SearchBar.tsx
│ │ └── TableItem.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ ├── reportWebVitals.ts
│ └── setupTests.ts
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
├── LICENSE
└── README.md
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/.DS_Store
--------------------------------------------------------------------------------
/.github/.images/dark-theme.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/.github/.images/dark-theme.jpg
--------------------------------------------------------------------------------
/.github/.images/formik-todo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/.github/.images/formik-todo.jpg
--------------------------------------------------------------------------------
/.github/.images/light-theme.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/.github/.images/light-theme.jpg
--------------------------------------------------------------------------------
/.github/.images/randomuserapi-table.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/.github/.images/randomuserapi-table.jpg
--------------------------------------------------------------------------------
/.github/.images/spotlight-search.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/.github/.images/spotlight-search.jpg
--------------------------------------------------------------------------------
/1. spotlight-search/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/1. spotlight-search/README.md:
--------------------------------------------------------------------------------
1 | # Spotlight Search
2 |
3 | [Spotligit Search](https://support.apple.com/guide/mac-help/spotlight-mchlp1008/mac) is one of the most useful tools in a Mac. It helps you quickly open any application, file, folder or a website from anywhere. This challenge uses the UI of Spotlight Search to create a simple word filter.
4 |
5 | ## Challenge Description
6 |
7 |
8 |
9 | In this challenge, you need to load all the words in an array. You need to filter the words as you type in the search bar and display the results in a dropdown. This is to test your knowledge is basic React workflow and JavaScript utilities.
10 |
11 | ## Hint
12 |
13 | Use `Array.prototype.filter()` with `String.prototype.includes()` method to filter out the words as you type.
14 |
15 | ```javascript
16 | words.filter((word) => word.toLowerCase().includes(value.toLowerCase())
17 | ```
18 |
--------------------------------------------------------------------------------
/1. spotlight-search/craco.config.js:
--------------------------------------------------------------------------------
1 | // craco.config.js
2 | module.exports = {
3 | style: {
4 | postcss: {
5 | plugins: [require("tailwindcss"), require("autoprefixer")],
6 | },
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/1. spotlight-search/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spotlight-search",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.2.0",
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "@types/jest": "^26.0.15",
11 | "@types/node": "^12.0.0",
12 | "@types/react": "^17.0.0",
13 | "@types/react-dom": "^17.0.0",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "react-scripts": "4.0.3",
17 | "typescript": "^4.1.2",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "scripts": {
21 | "start": "craco start",
22 | "build": "craco build",
23 | "test": "craco test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | },
44 | "devDependencies": {
45 | "autoprefixer": "^9",
46 | "postcss": "^7",
47 | "tailwindcss": "npm:@tailwindcss/postcss7-compat"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/1. spotlight-search/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/1. spotlight-search/public/favicon.ico
--------------------------------------------------------------------------------
/1. spotlight-search/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 | Spotlight Search
14 |
15 |
16 | You need to enable JavaScript to run this app.
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/1. spotlight-search/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/1. spotlight-search/public/logo192.png
--------------------------------------------------------------------------------
/1. spotlight-search/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/1. spotlight-search/public/logo512.png
--------------------------------------------------------------------------------
/1. spotlight-search/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/1. spotlight-search/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import words from "./resources/words";
4 | import InputField from "./components/InputField";
5 | import Suggestions from "./components/Suggestions";
6 |
7 | const App: React.FC<{}> = () => {
8 | // Input value
9 | const [value, setValue] = useState("");
10 |
11 | // Suggestions
12 | const [suggestions, setSuggestions] = useState>([]);
13 |
14 | // Function to filter the suggestions according to the current input value
15 | const getSuggestions = (value: string) => {
16 | if (value.length === 0) setSuggestions([]);
17 | else
18 | setSuggestions(
19 | words.filter((word) => word.toLowerCase().includes(value.toLowerCase()))
20 | );
21 | };
22 |
23 | // Function to perform onChange event in input
24 | const onInputChange = (e: React.ChangeEvent) => {
25 | setValue(e.target.value);
26 | getSuggestions(e.target.value);
27 | };
28 |
29 | // Function to perform onClick function on individual suggestions
30 | const onSuggestionClick = (choice: string) => {
31 | setValue(choice);
32 | setSuggestions([]);
33 | };
34 |
35 | return (
36 |
37 | 0}
41 | />
42 | {suggestions.length > 0 && (
43 |
44 | )}
45 |
46 | );
47 | };
48 |
49 | export default App;
50 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/assets/images/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/1. spotlight-search/src/assets/images/background.jpg
--------------------------------------------------------------------------------
/1. spotlight-search/src/components/InputField.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | value: string;
3 | hasResults: boolean;
4 | onChange: React.ChangeEventHandler | undefined;
5 | }
6 |
7 | const InputField: React.FC = ({ value, onChange, hasResults }) => {
8 | return (
9 |
52 | );
53 | };
54 |
55 | export default InputField;
56 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/components/Suggestions.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | suggestions: Array;
3 | onClick: (choice: string) => void;
4 | }
5 |
6 | const Suggestions: React.FC = ({ suggestions, onClick }) => {
7 | return (
8 |
9 | {suggestions.map((suggestion, index) => (
10 | {
13 | onClick(suggestion);
14 | }}
15 | className="text-white hover:bg-blue-500 cursor-pointer mt-1 rounded-sm pl-2 pr-2"
16 | >
17 | {suggestion}
18 |
19 | ))}
20 |
21 | );
22 | };
23 |
24 | export default Suggestions;
25 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
8 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
16 | monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/1. spotlight-search/src/resources/words.ts:
--------------------------------------------------------------------------------
1 | // List of random words related to Apple spotlight
2 | const words: Array = [
3 | "App Store",
4 | "Keychain Access",
5 | "Activity Monitor",
6 | "Automator",
7 | "Audio MIDI setup",
8 | "Apple Inc.",
9 | "Bluetooth File Exchange",
10 | "Battery",
11 | "Google Chrome",
12 | "Chess",
13 | "Docker",
14 | "Dock and Menu",
15 | "Displays",
16 | "Dictionary",
17 | "Disk Utility",
18 | "Desktop and Screensaver",
19 | "Date & Time",
20 | "Finder",
21 | "Font Book",
22 | "FaceTime",
23 | "Family Sharing",
24 | "Google Chrome",
25 | "Grapher",
26 | "Home",
27 | "Help Viewer",
28 | "iTerm",
29 | "Image Capture",
30 | "iMovie",
31 | "Keynote",
32 | "Keyboard",
33 | "Launchpad",
34 | "Mail",
35 | "Messages",
36 | "Storage Management",
37 | "Maps",
38 | "Notes",
39 | "Numbers",
40 | "Network Utility",
41 | "Networks",
42 | "iOS App Installer",
43 | "Preview",
44 | "Photos",
45 | "Photo Booth",
46 | "QuickTime Player",
47 | "Reminders",
48 | "Recents",
49 | "System Preferences",
50 | "Safari",
51 | "Slack",
52 | "Siri",
53 | ];
54 |
55 | export default words.sort();
56 |
--------------------------------------------------------------------------------
/1. spotlight-search/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {
6 | backgroundImage: (theme) => ({
7 | background: "url(assets/images/background.jpg)",
8 | }),
9 | },
10 | },
11 | variants: {
12 | extend: {},
13 | },
14 | plugins: [],
15 | };
16 |
--------------------------------------------------------------------------------
/1. spotlight-search/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/2. formik-todo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/2. formik-todo/README.md:
--------------------------------------------------------------------------------
1 | # Formik Todo
2 |
3 | If you have worked with React before, it is very likely that you have built a Todo application. This challenge is not just about building a Todo application, but also about building a form.
4 |
5 | Almost every website you build will have a form. Even if it is a trivial one, it throws a bunch of errors and eat up a lot of time. [Formik](https://formik.org) is one of the most popular libraries for building forms. It makes your lives easy. Just a few lines of code and viola, no more errors.
6 |
7 | ## Challenge Description
8 |
9 |
10 |
11 | In this challenge, you need to create a Todo application using Formik. It must be able to do all the usual stuff like adding a new Todo, marking a Todo as done, and deleting a Todo.
12 |
13 | ## Hint
14 |
15 | For removing a todo item, `Array.prototype.filter()` will come in handy. For marking a todo as completed, `Array.prototype.map()` would be your best choice.
16 |
17 | Make sure you have assigned a unique id to all the todo items. Assigning `index` as key would not be an ideal choice. On deleting an item, the index values of the remaining items gets reassigned, causing wierd errors. For more information about lists and keys, click [here](https://reactjs.org/docs/lists-and-keys.html).
18 |
--------------------------------------------------------------------------------
/2. formik-todo/craco.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | style: {
3 | postcss: {
4 | plugins: [require("tailwindcss"), require("autoprefixer")],
5 | },
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/2. formik-todo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.2.0",
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "@types/jest": "^26.0.15",
11 | "@types/node": "^12.0.0",
12 | "@types/react": "^17.0.0",
13 | "@types/react-dom": "^17.0.0",
14 | "formik": "^2.2.9",
15 | "react": "^17.0.2",
16 | "react-dom": "^17.0.2",
17 | "react-scripts": "4.0.3",
18 | "typescript": "^4.1.2",
19 | "web-vitals": "^1.0.1"
20 | },
21 | "scripts": {
22 | "start": "craco start",
23 | "build": "craco build",
24 | "test": "craco test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | },
45 | "devDependencies": {
46 | "autoprefixer": "^9",
47 | "postcss": "^7",
48 | "tailwindcss": "npm:@tailwindcss/postcss7-compat"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/2. formik-todo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/2. formik-todo/public/favicon.ico
--------------------------------------------------------------------------------
/2. formik-todo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | Formik Todo
15 |
16 |
17 | You need to enable JavaScript to run this app.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/2. formik-todo/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/2. formik-todo/public/logo192.png
--------------------------------------------------------------------------------
/2. formik-todo/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/2. formik-todo/public/logo512.png
--------------------------------------------------------------------------------
/2. formik-todo/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/2. formik-todo/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/2. formik-todo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import AddTodoBar from "./components/AddTodoBar";
3 | import TodoItem from "./components/TodoItem";
4 |
5 | interface Todo {
6 | id: string;
7 | title: string;
8 | description: string;
9 | completed: boolean;
10 | }
11 |
12 | const App: React.FC<{}> = () => {
13 | // Todo items
14 | const [todos, setTodos] = useState>([]);
15 |
16 | // Add a new todo item
17 | const addTodo = (title: string, description: string) => {
18 | setTodos([
19 | {
20 | id: `todo__${Math.random().toString().substr(2)}`,
21 | title,
22 | description,
23 | completed: false,
24 | },
25 | ...todos,
26 | ]);
27 | };
28 |
29 | // Cross off a todo item
30 | const completedTodo = (id: string) => {
31 | setTodos((todos) =>
32 | todos.map((todo) => {
33 | if (todo.id === id) {
34 | return { ...todo, completed: !todo.completed };
35 | } else return todo;
36 | })
37 | );
38 | };
39 |
40 | // Remove a todo item
41 | const removeTodo = (id: string) => {
42 | setTodos((todos) => todos.filter((todo) => todo.id !== id));
43 | };
44 |
45 | return (
46 |
47 |
49 | addTodo(title, description)
50 | }
51 | />
52 |
53 | {todos.map((todo) => {
54 | return (
55 | completedTodo(id)}
62 | removeTodo={(id: string) => removeTodo(id)}
63 | />
64 | );
65 | })}
66 |
67 | );
68 | };
69 |
70 | export default App;
71 |
--------------------------------------------------------------------------------
/2. formik-todo/src/components/AddTodoBar.tsx:
--------------------------------------------------------------------------------
1 | import { ErrorMessage, Field, Form, Formik } from "formik";
2 |
3 | interface Props {
4 | addTodo: (title: string, description: string) => void;
5 | }
6 |
7 | interface FormInterface {
8 | title: string;
9 | description: string;
10 | }
11 |
12 | const AddTodoBar: React.FC = ({ addTodo }) => {
13 | // Initial values of the form
14 | const initialValues = {
15 | title: "",
16 | description: "",
17 | };
18 |
19 | // Form validation
20 | const validate = (values: FormInterface) => {
21 | if (!values.title) return { title: "Title is required" };
22 | };
23 |
24 | // Form submission
25 | const onSubmit = (
26 | values: FormInterface,
27 | { resetForm }: { resetForm: () => void }
28 | ) => {
29 | addTodo(values.title, values.description);
30 | resetForm();
31 | };
32 |
33 | return (
34 |
40 |
99 |
100 | );
101 | };
102 |
103 | export default AddTodoBar;
104 |
--------------------------------------------------------------------------------
/2. formik-todo/src/components/TodoItem.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | id: string;
3 | title: string;
4 | description: string;
5 | completed: boolean;
6 | completedTodo: (id: string) => void;
7 | removeTodo: (id: string) => void;
8 | }
9 |
10 | const TodoItem: React.FC = ({
11 | id,
12 | title,
13 | description,
14 | completed,
15 | completedTodo,
16 | removeTodo,
17 | }) => {
18 | return (
19 |
37 |
38 |
completedTodo(id)}
42 | className="h-4 w-4 cursor-pointer mr-2 mt-1"
43 | />
44 |
45 |
53 | {title}
54 |
55 |
61 | {description}
62 |
63 |
64 |
65 |
removeTodo(id)}
72 | >
73 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default TodoItem;
85 |
--------------------------------------------------------------------------------
/2. formik-todo/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
8 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
16 | monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/2. formik-todo/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/2. formik-todo/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/2. formik-todo/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/2. formik-todo/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/2. formik-todo/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | };
12 |
--------------------------------------------------------------------------------
/2. formik-todo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/README.md:
--------------------------------------------------------------------------------
1 | # Dark Theme Toggle
2 |
3 | Things will start getting spicy here. I have chosen a complex UI for this one. The aim is to build it with code readability in mind. There are scopes to use higher order components (HOC) to wrap the stateless functional components.
4 |
5 | ## Challenge Description
6 |
7 |
8 |
9 |
10 | Try to recreate the system preferences UI for MacOS. The particular setting allows the users to change their wallpapers.
11 |
12 | The main challenge is the dark theme toggle. It is easy to implement it using [Tailwind CSS](https://tailwindcss.com) or [Styled Components](https://styled-components.com). Feel free to experiment it with any other library.
13 |
14 | Toggling dark theme is possible either by using a button, or by using the system theme. For this challenge, I've chosen the system theme version. Goto your system settings in your OS and switch theme from Dark to Light to see the difference.
15 |
16 | ## Hint
17 |
18 | In Tailwind CSS, you can use the `.dark` class to toggle the dark theme. First, edit your `tailwind.config.js` file to add the following line:
19 |
20 | ```diff
21 | - darkMode: false,
22 | + darkMode: "media",
23 | ```
24 |
25 | Now, the dark theme of the web app will be based on the system theme of your OS. For official documentation check [here](https://tailwindcss.com/docs/dark-mode).
26 |
27 | For Styled Components, I have found a good [Medium article](https://medium.com/swlh/create-a-dark-mode-of-your-app-using-styled-components-a44bc5a59330) on how to implement it.
28 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/craco.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | style: {
3 | postcss: {
4 | plugins: [require("tailwindcss"), require("autoprefixer")],
5 | },
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dark-theme-toggle",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.2.0",
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "@types/jest": "^26.0.15",
11 | "@types/node": "^12.0.0",
12 | "@types/react": "^17.0.0",
13 | "@types/react-dom": "^17.0.0",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "react-scripts": "4.0.3",
17 | "typescript": "^4.1.2",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "scripts": {
21 | "start": "craco start",
22 | "build": "craco build",
23 | "test": "craco test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | },
44 | "devDependencies": {
45 | "autoprefixer": "^9",
46 | "postcss": "^7",
47 | "tailwindcss": "npm:@tailwindcss/postcss7-compat"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/3. dark-theme-toggle/public/favicon.ico
--------------------------------------------------------------------------------
/3. dark-theme-toggle/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | Dark Theme Toggle
15 |
16 |
17 | You need to enable JavaScript to run this app.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/3. dark-theme-toggle/public/logo192.png
--------------------------------------------------------------------------------
/3. dark-theme-toggle/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/3. dark-theme-toggle/public/logo512.png
--------------------------------------------------------------------------------
/3. dark-theme-toggle/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import Navbar from "./components/Navbar/Navbar";
4 | import SystemPreferences from "./components/SystemPreferences/SystemPreferences";
5 |
6 | // Importing all wallpapers
7 | import Loki from "./assets/images/loki.jpg";
8 | import Batman from "./assets/images/batman.jpg";
9 | import Interstellar from "./assets/images/interstellar.jpg";
10 |
11 | const App: React.FC<{}> = () => {
12 | // List of all wallpapers
13 | const wallpapers = [Loki, Batman, Interstellar];
14 |
15 | // Current wallpaper
16 | const [wallpaper, setWallpaper] = useState(wallpapers[0]);
17 |
18 | return (
19 |
23 |
24 |
29 |
30 | );
31 | };
32 |
33 | export default App;
34 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/assets/icons/apple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/assets/icons/battery.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
10 |
11 |
13 |
14 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/assets/images/batman.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/3. dark-theme-toggle/src/assets/images/batman.jpg
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/assets/images/interstellar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/3. dark-theme-toggle/src/assets/images/interstellar.jpg
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/assets/images/loki.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/3. dark-theme-toggle/src/assets/images/loki.jpg
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/components/Navbar/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import AppleLogo from "../../assets/icons/apple.svg";
2 | import BatteryIcon from "../../assets/icons/battery.svg";
3 |
4 | // Get current date and time
5 | const getDateAndTime = () => {
6 | const date = new Date();
7 | return `${date.toLocaleTimeString("en-US", {
8 | hour: "2-digit",
9 | minute: "2-digit",
10 | day: "2-digit",
11 | month: "short",
12 | weekday: "short",
13 | })}`;
14 | };
15 |
16 | const Navbar: React.FC<{}> = () => {
17 | const menuItems: Array = ["Edit", "View", "Window", "Help"];
18 |
19 | return (
20 |
21 | {/* Menu bar */}
22 |
23 |
28 |
29 | System Preferences
30 |
31 | {menuItems.map((item, index) => (
32 |
36 | {item}
37 |
38 | ))}
39 |
40 |
41 | {/* Icons */}
42 |
43 |
44 |
100%
45 |
46 |
47 |
54 |
60 |
61 |
68 |
74 |
75 |
76 | {getDateAndTime()}
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | export default Navbar;
84 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/components/SystemPreferences/Frame/Frame.tsx:
--------------------------------------------------------------------------------
1 | const Frame: React.FC<{}> = ({ children }) => {
2 | return (
3 |
4 | {/* Top bar */}
5 |
6 |
7 |
8 | x
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
22 |
28 |
29 |
36 |
42 |
43 |
44 |
51 |
57 |
58 |
59 | Desktop & Screen Saver
60 |
61 |
83 |
84 |
85 |
86 |
87 | Desktop
88 |
89 |
90 | Screen Saver
91 |
92 |
93 |
94 | {children}
95 |
96 |
97 |
98 | );
99 | };
100 |
101 | export default Frame;
102 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/components/SystemPreferences/SideBar/SideBar.tsx:
--------------------------------------------------------------------------------
1 | const SideBar: React.FC<{}> = () => {
2 | return (
3 |
4 |
5 |
12 |
18 |
19 | Apple
20 |
21 |
22 |
23 |
30 |
36 |
37 | Desktop Pictures
38 |
39 |
40 |
47 |
53 |
54 | Colors
55 |
56 |
57 |
58 |
65 |
71 |
72 | Photos
73 |
74 |
75 |
76 |
83 |
89 |
90 | Favorites
91 |
92 |
93 |
100 |
106 |
107 | People
108 |
109 |
110 |
117 |
123 |
124 | Shared
125 |
126 |
127 |
134 |
140 |
141 | Albums
142 |
143 |
144 |
145 |
152 |
158 |
159 | Folders
160 |
161 |
162 |
163 |
170 |
176 |
177 | Pictures
178 |
179 |
180 |
181 |
182 |
183 | );
184 | };
185 |
186 | export default SideBar;
187 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/components/SystemPreferences/SystemPreferences.tsx:
--------------------------------------------------------------------------------
1 | import Frame from "./Frame/Frame";
2 | import SideBar from "./SideBar/SideBar";
3 |
4 | interface Props {
5 | wallpaper: string;
6 | wallpapers: Array;
7 | onWallpaperChange: (wallpaper: string) => void;
8 | }
9 |
10 | const SystemPreferences: React.FC = ({
11 | wallpaper,
12 | wallpapers,
13 | onWallpaperChange,
14 | }) => {
15 | return (
16 |
17 | {/* Wallpaper Preview */}
18 |
19 |
24 |
25 |
26 |
Fill Screen
27 |
28 |
35 |
41 |
42 |
49 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | {/* Wallpaper Select */}
63 |
64 |
65 |
66 | {wallpapers.map((wallpaper, index) => (
67 |
onWallpaperChange(wallpaper)}
73 | />
74 | ))}
75 |
76 |
77 |
78 |
79 |
80 |
81 | +
82 |
83 |
-
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default SystemPreferences;
91 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16 | monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
3 | darkMode: "media", // or 'media' or 'class'
4 | theme: {
5 | extend: {
6 | colors: {
7 | dark: {
8 | title: "#3B3B3B",
9 | innerBody: "#323232",
10 | outerBody: "#1E1E1E",
11 | dropDowns: "#656565",
12 | },
13 | },
14 | },
15 | },
16 | variants: {
17 | extend: {},
18 | },
19 | plugins: [],
20 | };
21 |
--------------------------------------------------------------------------------
/3. dark-theme-toggle/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/README.md:
--------------------------------------------------------------------------------
1 | # Random User API Table
2 |
3 | This challenge steps up a little. All the real world applications that you build will involve API calls. For this challenge, you have to build a table that displays random users from [randomuser.me api](https://randomuser.me/api/) API.
4 |
5 | ## Challenge Description
6 |
7 |
8 |
9 | Make an API call to [https://randomuser.me/api?results=500](https://randomuser.me/api?results=50) on first render. It will return a JSON array of 50 random users. Display selected fields of each user in the form of a table. The complete API documentation can be found [here](https://randomuser.me/).
10 |
11 | Add a search field to the table. When the user enters a search term, filter out the users based on their name, gender, or email id.
12 |
13 | Make the column headers of the table as a sorting button. It must be able to sort it in ascending as well as descending order.
14 |
15 | The users must have the provision to select multiple rows, and export it as csv.
16 |
17 | ## Hint
18 |
19 | [axios](https://www.npmjs.com/package/axios) is a popular library for making API calls. The documentation is self explanatory.
20 |
21 | There is an easy hack for filtering the rows based on the search input. All the users are represented in the form on javascript objects. `JSON.stringify` converts them into a string. A simple `String.prototype.substr()` can be used to check if the filter is applicable.
22 |
23 | ```javascript
24 | users.filter((user) =>
25 | JSON.stringify(Object.values(user))
26 | .toLowerCase()
27 | .includes(search.toLowerCase())
28 | ),
29 | ```
30 |
31 | Date formatting can be easily done using `Date.prototype.toLocaleDateString()` instead of depending on heavy third party packages like [moment](https://www.npmjs.com/package/moment).
32 |
33 | [react-csv](https://www.npmjs.com/package/react-csv) will come in handy to help with the csv export.
34 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/craco.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | style: {
3 | postcss: {
4 | plugins: [require("tailwindcss"), require("autoprefixer")],
5 | },
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "randomuser-api-table",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.2.0",
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "@types/jest": "^26.0.15",
11 | "@types/node": "^12.0.0",
12 | "@types/react": "^17.0.0",
13 | "@types/react-dom": "^17.0.0",
14 | "axios": "^0.21.1",
15 | "react": "^17.0.2",
16 | "react-csv": "^2.0.3",
17 | "react-dom": "^17.0.2",
18 | "react-scripts": "4.0.3",
19 | "typescript": "^4.1.2",
20 | "web-vitals": "^1.0.1"
21 | },
22 | "scripts": {
23 | "start": "craco start",
24 | "build": "craco build",
25 | "test": "craco test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app",
31 | "react-app/jest"
32 | ]
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | },
46 | "devDependencies": {
47 | "@types/react-csv": "^1.1.2",
48 | "autoprefixer": "^9",
49 | "postcss": "^7",
50 | "tailwindcss": "npm:@tailwindcss/postcss7-compat"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/4. randomuser-api-table/public/favicon.ico
--------------------------------------------------------------------------------
/4. randomuser-api-table/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | RandomUserAPI Table
15 |
16 |
17 | You need to enable JavaScript to run this app.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/4. randomuser-api-table/public/logo192.png
--------------------------------------------------------------------------------
/4. randomuser-api-table/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhayVAshokan/React-Challenges/9f106d2aafded36b38643bd9a4a00d45049773b8/4. randomuser-api-table/public/logo512.png
--------------------------------------------------------------------------------
/4. randomuser-api-table/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/App.tsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { useEffect, useState } from "react";
3 | import SearchBar from "./components/SearchBar";
4 | import TableItem from "./components/TableItem";
5 |
6 | // Interface for API response
7 | interface APIResponse {
8 | name: {
9 | title: string;
10 | first: string;
11 | last: string;
12 | };
13 | gender: string;
14 | dob: {
15 | date: string;
16 | };
17 | email: string;
18 | picture: {
19 | thumbnail: string;
20 | };
21 | }
22 |
23 | // Interface for table data
24 | interface User {
25 | name: string;
26 | gender: string;
27 | dob: string;
28 | email: string;
29 | picture: string;
30 | selected: boolean;
31 | }
32 |
33 | // Interface for sortConfig
34 | interface SortConfig {
35 | name: Direction;
36 | gender: Direction;
37 | dob: Direction;
38 | email: Direction;
39 | }
40 |
41 | // Direction of arrow inside table header element
42 | enum Direction {
43 | UP,
44 | DOWN,
45 | HIDDEN,
46 | }
47 |
48 | // Sort users according to the selected sort config
49 | // @ts-ignore
50 | const sortUsers: Array = (users: Array, sortConfig: SortConfig) => {
51 | // Sort by name
52 | if (sortConfig.name !== Direction.HIDDEN)
53 | return users.sort((user1: User, user2: User) => {
54 | return sortConfig.name === Direction.DOWN
55 | ? user1.name < user2.name
56 | ? -1
57 | : 1
58 | : user1.name > user2.name
59 | ? -1
60 | : 1;
61 | });
62 | // Sort by gender
63 | if (sortConfig.gender !== Direction.HIDDEN)
64 | return users.sort((user1: User, user2: User) => {
65 | return sortConfig.gender === Direction.DOWN
66 | ? user1.gender < user2.gender
67 | ? -1
68 | : 1
69 | : user1.gender > user2.gender
70 | ? -1
71 | : 1;
72 | });
73 | // Sort by email
74 | if (sortConfig.email !== Direction.HIDDEN)
75 | return users.sort((user1: User, user2: User) => {
76 | return sortConfig.email === Direction.DOWN
77 | ? user1.email < user2.email
78 | ? -1
79 | : 1
80 | : user1.email > user2.email
81 | ? -1
82 | : 1;
83 | });
84 | // Sort by dob
85 | if (sortConfig.dob !== Direction.HIDDEN)
86 | return users.sort((user1: User, user2: User) => {
87 | return sortConfig.dob === Direction.DOWN
88 | ? new Date(user1.dob) < new Date(user2.dob)
89 | ? -1
90 | : 1
91 | : new Date(user1.dob) > new Date(user2.dob)
92 | ? -1
93 | : 1;
94 | });
95 | };
96 |
97 | const App: React.FC<{}> = () => {
98 | // List of all users fetched by API
99 | const [users, setUsers] = useState>([]);
100 |
101 | // Table header items
102 | const headers = ["Name", "Gender", "DOB", "Email"];
103 |
104 | // Search item value
105 | const [value, setValue] = useState("");
106 |
107 | // Function to perform filtering operation on search
108 | const onSearchItemChange:
109 | | React.ChangeEventHandler
110 | | undefined = (e) => {
111 | setValue(e.target.value);
112 | };
113 |
114 | // Config to sort table items
115 | const [sortConfig, setSortConfig] = useState({
116 | name: Direction.DOWN,
117 | gender: Direction.HIDDEN,
118 | dob: Direction.HIDDEN,
119 | email: Direction.HIDDEN,
120 | });
121 |
122 | // Function to return all users filtered by the entered value
123 | const filterUsers = (search: string) => {
124 | // @ts-ignore
125 | return sortUsers(
126 | users.filter((user) =>
127 | JSON.stringify(Object.values(user))
128 | .toLowerCase()
129 | .includes(search.toLowerCase())
130 | ),
131 | sortConfig
132 | );
133 | };
134 |
135 | // Function to get export data
136 | const getExportData = (value: string) => {
137 | const allData = filterUsers(value);
138 | if (allData.some((user: User) => user.selected))
139 | return allData
140 | .filter((user: User) => user.selected === true)
141 | .map((user: User) => ({
142 | name: user.name,
143 | gender: user.gender,
144 | dob: new Date(user.dob).toLocaleDateString("en-US", {
145 | year: "numeric",
146 | month: "long",
147 | day: "numeric",
148 | }),
149 | email: user.email,
150 | }));
151 | else
152 | return allData.map((user: User) => ({
153 | name: user.name,
154 | gender: user.gender,
155 | dob: new Date(user.dob).toLocaleDateString("en-US", {
156 | year: "numeric",
157 | month: "long",
158 | day: "numeric",
159 | }),
160 | email: user.email,
161 | }));
162 | };
163 |
164 | // Populating users on first render
165 | useEffect(() => {
166 | axios.get("https://randomuser.me/api?results=50").then((res) => {
167 | setUsers(
168 | res.data.results.map((item: APIResponse) => ({
169 | name: `${item.name.title}. ${item.name.first} ${item.name.last}`,
170 | gender: item.gender,
171 | dob: item.dob.date,
172 | email: item.email,
173 | picture: item.picture.thumbnail,
174 | selected: false,
175 | }))
176 | );
177 | });
178 | }, []);
179 |
180 | // Return table header item
181 | const getTableHeaderItem = (title: string) => {
182 | // Key for sortConfig
183 | const headerKey = `${title.toLowerCase()}`;
184 |
185 | // @ts-ignore
186 | let direction = sortConfig[headerKey];
187 |
188 | const onClick = () => {
189 | if (direction === Direction.HIDDEN) direction = Direction.DOWN;
190 | else
191 | direction = direction === Direction.UP ? Direction.DOWN : Direction.UP;
192 |
193 | const config = {
194 | name: Direction.HIDDEN,
195 | gender: Direction.HIDDEN,
196 | dob: Direction.HIDDEN,
197 | email: Direction.HIDDEN,
198 | };
199 |
200 | if (headerKey === "name") config.name = direction;
201 | else if (headerKey === "email") config.email = direction;
202 | else if (headerKey === "dob") config.dob = direction;
203 | else config.gender = direction;
204 |
205 | setSortConfig({
206 | ...config,
207 | });
208 | };
209 |
210 | return (
211 |
212 |
213 |
226 | {title}
227 |
228 | {direction === Direction.UP ? (
229 |
236 |
242 |
243 | ) : direction === Direction.DOWN ? (
244 |
251 |
257 |
258 | ) : (
259 |
260 | )}{" "}
261 |
262 |
263 | );
264 | };
265 |
266 | return (
267 |
268 |
273 |
274 |
275 | {headers.map((header) => getTableHeaderItem(header))}
276 |
277 |
278 | {filterUsers(value).map((user: User) => (
279 |
288 | setUsers((users) =>
289 | users.map((_user) => {
290 | if (user.name === _user.name)
291 | return { ..._user, selected: !_user.selected };
292 | else return _user;
293 | })
294 | )
295 | }
296 | />
297 | ))}
298 |
299 |
300 |
301 | );
302 | };
303 |
304 | export default App;
305 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/components/ExportDropdown.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import { CSVLink } from "react-csv";
4 |
5 | interface Props {
6 | exportData: Array<{
7 | name: string;
8 | gender: string;
9 | dob: string;
10 | email: string;
11 | }>;
12 | }
13 |
14 | const ExportDropdown: React.FC = ({ exportData }) => {
15 | // Toggle show/hide dropdown
16 | const [showDropdown, setShowDropdown] = useState(false);
17 |
18 | return (
19 |
20 |
setShowDropdown((show: boolean) => !show)}
23 | >
24 |
31 |
37 |
38 | Export
39 |
40 | {showDropdown && (
41 |
42 | Export as .csv
43 |
44 | )}
45 |
46 | );
47 | };
48 |
49 | export default ExportDropdown;
50 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/components/SearchBar.tsx:
--------------------------------------------------------------------------------
1 | import ExportDropdown from "./ExportDropdown";
2 |
3 | interface Props {
4 | value: string;
5 | onChange: React.ChangeEventHandler | undefined;
6 | exportData: Array<{
7 | name: string;
8 | gender: string;
9 | dob: string;
10 | email: string;
11 | }>;
12 | }
13 |
14 | const SearchBar: React.FC = ({ value, onChange, exportData }) => {
15 | return (
16 |
41 | );
42 | };
43 |
44 | export default SearchBar;
45 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/components/TableItem.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | name: string;
3 | gender: string;
4 | dob: Date;
5 | email: string;
6 | picture: string;
7 | selected: boolean;
8 | onClick: () => void;
9 | }
10 |
11 | // Format date to Month (long) date (numeric), year (long) format
12 | const getFormattedDate = (date: Date) => {
13 | return date.toLocaleDateString("en-US", {
14 | year: "numeric",
15 | month: "long",
16 | day: "numeric",
17 | });
18 | };
19 |
20 | const TableItem: React.FC = ({
21 | name,
22 | gender,
23 | dob,
24 | email,
25 | picture,
26 | selected,
27 | onClick,
28 | }) => {
29 | return (
30 |
44 |
45 |
46 |
{" "}
51 |
{name}
52 |
53 |
54 | {gender}
55 | {getFormattedDate(dob)}
56 | {email}
57 |
58 | );
59 | };
60 |
61 | export default TableItem;
62 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
8 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
16 | monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | };
12 |
--------------------------------------------------------------------------------
/4. randomuser-api-table/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Abhay V Ashokan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Challenges
2 |
3 | [](https://AbhayVAshokan.github.io/React-Challenges)
4 | [](https://github.com/AbhayVAshokan/React-Challenges/blob/master/LICENSE)
5 |
6 | All the different challenges test different levels of your knowledge in JavaScript and React. It is best if you would do it on your own instead of just reading my code. I promise you you will learn a lot. I did learn a lot just by preparing these.
7 |
8 | I have used [React (TypeScript)](https://create-react-app.dev/docs/adding-typescript/) and [Tailwind CSS](https://tailwindcss.com/docs/guides/create-react-app) for all the challeges. You are free to use any library you prefer. You can modify the UI as you like. Just make sure you get the functionalities right, because that's what the challenges are all about.
9 |
10 | **_Open each folder to get a detailed description of each challenge._**
11 |
12 | ### Final Results: [React Challenges](https://AbhayVAshokan.github.io/React-Challenges)
13 |
14 | ## Challenges
15 |
16 | ### 1. Spotlight Search (Very Easy)
17 |
18 | [Spotligit Search](https://support.apple.com/guide/mac-help/spotlight-mchlp1008/mac) is one of the most useful tools in a Mac. It helps you quickly open any application, file, folder or a website from anywhere. This challenge uses the UI of Spotlight Search to create a simple word filter.
19 |
20 | In this challenge, load all the words in an array. You need to filter the words as you type in the search bar and display the results in a dropdown. This is to test your knowledge is basic React workflow and JavaScript utilities.
21 |
22 |
23 |
24 | ---
25 |
26 | ### 2. Formik Todo (Easy)
27 |
28 | If you have worked with React before, it is very likely that you have built a Todo application. This challenge is not just about building a Todo application, but also about building a form.
29 |
30 | Almost every website you build will have a form. Even if it is a trivial one, it throws a bunch of errors and eat up a lot of time. [Formik](https://formik.org) is one of the most popular libraries for building forms. It makes your lives easy. Just a few lines of code and viola, no more errors.
31 |
32 | In this challenge, you need to create a Todo application using Formik. It must be able to do all the usual stuff like adding a new Todo, marking a Todo as done, and deleting a Todo.
33 |
34 |
35 |
36 | ---
37 |
38 | ### 3. Dark Theme Toggle (Intermediate)
39 |
40 | Things will start getting spicy here. I have chosen a complex UI for this one. The aim is to build it with code readability in mind. There are scopes to use higher order components (HOC) to wrap the stateless functional components.
41 |
42 | The main challenge is the dark theme toggle. It is easy to implement it using [Tailwind CSS](https://tailwindcss.com) or [Styled Components](https://styled-components.com). Feel free to experiment it with any other library.
43 |
44 | Toggling dark theme is possible either by using a button, or by using the system theme. For this challenge, I've chosen the system theme version. Goto your system settings in your OS and switch theme from Dark to Light to see the difference.
45 |
46 |
47 |
48 |
49 | ---
50 |
51 | ### 4. randomuserapi Table (Advanced)
52 |
53 | This challenge steps up a little. All the real world applications that you build will involve API calls. For this challenge, you have to build a table that displays random users from [randomuser.me api](https://randomuser.me/) API.
54 |
55 | Well, that's the easy part. The table must have a search feature to filter out the users based on the search input (name, gender, email). The user can click on the table headers to sort according to the selected column (ascending and descending).
56 |
57 | The users must have the provision to select multiple rows, and export it as csv.
58 |
59 |
60 |
61 | ---
62 |
63 | ### 5. React Router and Redux (Expert)
64 |
65 | I am looking for a fun idea that is not too long to be boring, but complex enough to be an expert category. If you have any good ideas, please share them here: [Issue#1](https://github.com/AbhayVAshokan/React-Challenges/issues/1).
66 |
67 | ## Setup
68 |
69 | If you want to have the exact setup as mine, then follow the steps below. But if you want to use your own setup, then you can skip this section.
70 |
71 | 1. Create a React (TypeScript) project
72 |
73 | ```bash
74 | npx create-react-app --template=typescript
75 | ```
76 |
77 | 2. Add Tailwind CSS to your project: [Official Documentation](https://tailwindcss.com/docs/guides/create-react-app)
78 |
79 | Try to keep third party libraries to a minimum.
80 |
81 | ## Contribution
82 |
83 | I started working these projects just to kill some time. Soon I realized that it could do much more than that. Feel free to pull request new challenges or improve existing ones. The aim is to make it a good learning resource for everyone, and trust me, if you can build these on your own, then you are ready to call yourselves a react expert :)
84 |
--------------------------------------------------------------------------------