├── .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 | Spotlight Search 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 | 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 |
30 | 37 | 43 | 44 | 51 |
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 | Formik Todo 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 | 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 |
41 |
42 | 49 | 55 | 56 |
57 | 63 | 68 |
69 |
70 | 71 |
72 | 78 | 97 |
98 |
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 | Light Theme 8 | Dark Theme 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 | 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 | 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 |
62 |
63 | 70 | 76 | 77 | 81 |
82 |
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 | Wallpaper preview 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 | Wallpaper Thumbnail 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 | randomuser-api table 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 | 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 | 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 |
17 |
18 | 25 | 31 | 32 | 38 |
39 | 40 |
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 | {name}{" "} 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 | [![Website shields.io](https://img.shields.io/website-up-down-green-red/http/shields.io.svg)](https://AbhayVAshokan.github.io/React-Challenges) 4 | [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](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 | Spotlight Search 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 | Formik Todo 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 | Light Theme 47 | Dark Theme 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 | randomuser-api table 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 | --------------------------------------------------------------------------------