diff --git a/react-router-v6/.babelrc b/react-router-v6/.babelrc new file mode 100644 index 0000000..4f06b0c --- /dev/null +++ b/react-router-v6/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ] +} diff --git a/react-router-v6/.gitignore b/react-router-v6/.gitignore new file mode 100644 index 0000000..9b9efe6 --- /dev/null +++ b/react-router-v6/.gitignore @@ -0,0 +1,107 @@ +# Others +build* + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/react-router-v6/README.md b/react-router-v6/README.md new file mode 100644 index 0000000..96a193b --- /dev/null +++ b/react-router-v6/README.md @@ -0,0 +1,18 @@ +# React.js Example + +## Table of Contents +0. [General](#general) +1. [Links](#links) + +# General + +# Links + +* [React Router v6.1](https://reactrouter.com/en/main/start/tutorial) +* [React setup with webpack for beginners](https://dev.to/deepanjangh/react-setup-with-webpack-for-beginners-2a8k) +* [Production](https://webpack.js.org/guides/production/) +* [Setup Development and Production Environment for React App](https://medium.com/freestoneinfotech/setup-development-and-production-environment-for-react-app-397c4cc9e382) +* [HtmlWebpackPlugin](https://webpack.js.org/plugins/html-webpack-plugin/) +* [load CSS](https://masteringjs.io/tutorials/webpack/css-loader) +* [load CSS](https://webpack.js.org/loaders/css-loader/) +* [load CSS](https://blog.jakoblind.no/css-modules-webpack/) diff --git a/react-router-v6/app/app.jsx b/react-router-v6/app/app.jsx new file mode 100644 index 0000000..009d5a9 --- /dev/null +++ b/react-router-v6/app/app.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +export default function App() { + return ( + <> +

App

+ + ); +}; diff --git a/react-router-v6/app/components/hello.jsx b/react-router-v6/app/components/hello.jsx new file mode 100644 index 0000000..2904898 --- /dev/null +++ b/react-router-v6/app/components/hello.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +//destructure props +const Hello = ({msg}) => { + return ( + <> +
{msg}
+ + ); +} + +export default Hello + +Hello.propTypes = { + msg: PropTypes.string, +}; diff --git a/react-router-v6/app/contacts.js b/react-router-v6/app/contacts.js new file mode 100644 index 0000000..07659a6 --- /dev/null +++ b/react-router-v6/app/contacts.js @@ -0,0 +1,73 @@ +import localforage from "localforage"; +import { matchSorter } from "match-sorter"; +import sortBy from "sort-by"; + +export async function getContacts(query) { + await fakeNetwork(`getContacts:${query}`); + let contacts = await localforage.getItem("contacts"); + if (!contacts) contacts = []; + if (query) { + contacts = matchSorter(contacts, query, { keys: ["first", "last"] }); + } + return contacts.sort(sortBy("last", "createdAt")); +} + +export async function createContact() { + await fakeNetwork(); + let id = Math.random().toString(36).substring(2, 9); + let contact = { id, createdAt: Date.now() }; + let contacts = await getContacts(); + contacts.unshift(contact); + await set(contacts); + return contact; +} + +export async function getContact(id) { + await fakeNetwork(`contact:${id}`); + let contacts = await localforage.getItem("contacts"); + let contact = contacts.find(contact => contact.id === id); + return contact ?? null; +} + +export async function updateContact(id, updates) { + await fakeNetwork(); + let contacts = await localforage.getItem("contacts"); + let contact = contacts.find(contact => contact.id === id); + if (!contact) throw new Error("No contact found for", id); + Object.assign(contact, updates); + await set(contacts); + return contact; +} + +export async function deleteContact(id) { + let contacts = await localforage.getItem("contacts"); + let index = contacts.findIndex(contact => contact.id === id); + if (index > -1) { + contacts.splice(index, 1); + await set(contacts); + return true; + } + return false; +} + +function set(contacts) { + return localforage.setItem("contacts", contacts); +} + +// fake a cache so we don't slow down stuff we've already seen +let fakeCache = {}; + +async function fakeNetwork(key) { + if (!key) { + fakeCache = {}; + } + + if (fakeCache[key]) { + return; + } + + fakeCache[key] = true; + return new Promise(res => { + setTimeout(res, Math.random() * 800); + }); +} diff --git a/react-router-v6/app/error-page.jsx b/react-router-v6/app/error-page.jsx new file mode 100644 index 0000000..b2ac4ee --- /dev/null +++ b/react-router-v6/app/error-page.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useRouteError } from "react-router-dom"; + +export default function ErrorPage() { + const error = useRouteError(); + console.error('error: '+error); + + return ( +
+

Oops!

+

Sorry, an unexpected error has occurred.

+

+ {error.statusText || error.message} +

+
+ ); +} diff --git a/react-router-v6/app/index.jsx b/react-router-v6/app/index.jsx new file mode 100644 index 0000000..3a7380d --- /dev/null +++ b/react-router-v6/app/index.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { + createBrowserRouter, + RouterProvider, +} from 'react-router-dom'; + +//import root route +import Root from './routes/root'; + +//import error route +//TODO Why is this page not working? +import ErrorPage from './error-page'; + +//import contact route +//TODO Why is this page not working? +import Contact from './routes/contact'; + +//create Browser Router and configure route +const router=createBrowserRouter([ + { + path:"/", + element:, + errorElement:, + }, + { + path:"contacts/:contactId", + element:, + }, +]); + +//TODO remove debugging +if (process.env.NODE_ENV !== 'production') { + console.log('development mode'); +} + +//since react 18 +import { createRoot } from 'react-dom/client'; +//create root container +const root = createRoot(document.getElementById("root")); +//render root app +root.render( + + + +); diff --git a/react-router-v6/app/pages/home.jsx b/react-router-v6/app/pages/home.jsx new file mode 100644 index 0000000..c8fcf32 --- /dev/null +++ b/react-router-v6/app/pages/home.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Hello from '../components/hello'; +import '../style.css'; +const Home = () => { + return ( + <> +

Home

+

(React.js Lambda Function Component)

+ + + ); +} + +export default Home diff --git a/react-router-v6/app/routes/contact.jsx b/react-router-v6/app/routes/contact.jsx new file mode 100644 index 0000000..e861ad7 --- /dev/null +++ b/react-router-v6/app/routes/contact.jsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Form } from "react-router-dom"; + +export default function Contact() { + const contact = { + first: "Your", + last: "Name", + avatar: "https://placekitten.com/g/200/200", + twitter: "your_handle", + notes: "Some notes", + favorite: true, + }; + + return ( +
+
+ +
+ +
+

+ {contact.first || contact.last ? ( + <> + {contact.first} {contact.last} + + ) : ( + No Name + )}{" "} + +

+ + {contact.twitter && ( +

+ + {contact.twitter} + +

+ )} + + {contact.notes &&

{contact.notes}

} + +
+
+ +
+
{ + if ( + !confirm( + "Please confirm you want to delete this record." + ) + ) { + event.preventDefault(); + } + }} + > + +
+
+
+
+ ); +} + +function Favorite({ contact }) { + // yes, this is a `let` for later + let favorite = contact.favorite; + return ( +
+ +
+ ); +} diff --git a/react-router-v6/app/routes/root.jsx b/react-router-v6/app/routes/root.jsx new file mode 100644 index 0000000..7ffa228 --- /dev/null +++ b/react-router-v6/app/routes/root.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +export default function Root() { + return ( + <> +