# Learning Note of Online-Gallery app **This note was created through collaboration with ChatGPT.** **Inroduction** React is a component-based framework that uses JSX in a single JavaScript file, allowing developers to avoid the messiness of working with separate HTML files. Collaborative tools like Adobe XD or Figma are often used for UI design, enabling developers to work on framework and prototype components, making each component reusable, similar to completing a Lego game. Components transfer information using props, passing data from top to bottom in the component tree. This is why most declarations of Hooks constants are typically placed at the top level of each component. ## Basic Hooks in React ### useState - `useState` is a basic hook that allows developers to manage state within functional components. - It returns a stateful value and a function to update that value. - You can assign simple structured objects to state variables. #### Example ```javascript const [varName, setVarName] = useState(initialValue); ``` - In the above example, varName is the state variable that holds the current value. - initialValue is the initial value of the state variable. - setVarName is the function that can be used to update the value of varName. - To access the value of varName, you can use {varName} in your JSX code. - To update the value of varName, you can use setVarName(newValue). ### useReducer - `useReducer` is another hook that is used for managing more complex state and state transitions. - It takes in a reducer function and an initial state, and returns the current state and a dispatch function. - The reducer function receives the current state and an action object and returns the new state. #### Example implamentation within project ```javascript const initialState = { items: [], inputs: { title: null, file: null, path: null } }; function reducer(state, action) { switch (action.type) { case 'UPDATE_ITEMS': return { ...state, items: action.payload }; case 'UPDATE_INPUTS': return { ...state, inputs: action.payload }; default: return state; } } ``` - In the above example, `initialState` is an object representing the initial state with properties `items` and `inputs`. - We define a `reducer` function that handles different action types and updates the state accordingly. - Using the `useReducer` hook, we initialize the state with `initialState` and get the current state and `dispatch` function. - We can then dispatch actions by calling `dispatch` with an action object containing a `type` and a `payload`. ```javascript const [state, dispatch] = useReducer(reducer, initialState); dispatch({ type: 'UPDATE_ITEMS', payload: ['item1', 'item2', 'item3'] }); dispatch({ type: 'UPDATE_INPUTS', payload: { title: 'Sample', file: 'sample.txt', path: '/sample' } }); ``` ### useMemo - `useMemo` is a hook used for memoization and optimizing performance. - It takes a function and an array of dependencies as arguments and returns a memoized value. - The memoized value is only recomputed when one of the dependencies changes. #### Example ```javascript const memoizedValue = useMemo(() => { return someValue; }, [dependency1, dependency2]); ``` - In the above example, the function or computation inside `useMemo` will only be executed when `dependency1` or `dependency2` changes. - The result of the computation is stored in `memoizedValue`, which can be used in your component. ### useEffect - `useEffect` is a special hook used to handle side effects and interactions in functional components. - It allows you to perform tasks such as data fetching, subscriptions, or manually changing the DOM after the component has rendered. - The first argument of `useEffect` is a function that will be called after the component has rendered and any time the dependencies specified in the second argument change. - The second argument is an optional dependencies array that determines when the effect should be re-run. If the array is empty, the effect runs only once (similar to `componentDidMount` in class components). - If the second argument is not specified, it can lead to an infinite loop as the effect will run after every render. #### Example ```javascript useEffect(() => { // This function will be called after every render and when dependencies change }, [dependency1, dependency2]); ``` ### useContext - The `useContext` hook allows React developers to follow the "single source of truth" principle and avoid props drilling. - It consists of two parts: the context provider and the context consumer. - In a scenario where props drilling occurs, passing props through multiple components can become cumbersome and error-prone. To mitigate this issue, the `useContext` hook can be used. #### Context Provider - The context provider is a component that wraps around a group of components and provides them with a specific context value. - It uses the `<Context.Provider>` component with a `value` prop to pass the context value to its children. - By using the context provider, you establish a shared context that can be accessed by nested components. #### Context Consumer - The context consumer is a component that accesses the context value provided by the context provider. - It uses the `useContext` hook to access the context value at the desired layer in the component tree. - By using `useContext`, you can directly access the context value without passing it through intermediate components. #### Example **Step 1: createContext** To create a context object, use the `createContext()` function: ```jsx // Create the context object const MyContext = createContext(); ``` **Step 2: Context.Provider** To provide the context value to components, use the context provider: ```jsx <MyContext.Provider value={props}> {Children} </MyContext.Provider> ``` **Step 3: Context.Consumer** To access the context value, use the context consumer with the `useContext` hook: ```jsx // Context consumer const contextValue = useContext(MyContext); ``` ### Concepts and Potential Issues - **Context Dependency**: Make sure the component using `useContext` depends on the context value by passing the necessary context dependencies. - **Provider Hierarchy**: Properly nest the context provider and consumer within the component hierarchy to ensure the consumer can access the context value. - **Avoid Overusing Context**: Use context for global state or data that needs to be accessed by multiple components, but avoid overusing it for every piece of data in your application. - **Performance Considerations**: Optimize performance by memoizing the context value or using memoization techniques like `useMemo` when the context value changes frequently. - **Testing Considerations**: Provide a suitable context value for testing components that use `useContext` by mocking or providing a mock context value. ## Firebase To learn how to implement Firebase in your React.js app, follow these steps: ### Step 1: Create Firebase Account and Project - Create an account on Firebase. - Set up a new project. ### Step 2: Register as Web Application and Install Firebase - Register your app as a Web Application within the Firebase project. - Install Firebase in your project using the command `npm install firebase`. - Create a configuration file and copy-paste the settings from the project configuration in Firebase. ### Step 3: Set up Firestore Database and Add Handler Functions Implement the following functions within the Firestore SDK: ```javascript writeDoc: (...args) => { const [inputs, collection_name] = args; return new Promise(async resolve => { const randomIndex = Math.floor(Math.random() * 100000000); try { const docRef = doc(db, collection_name, `${randomIndex}`); await setDoc(docRef, { title: inputs.title, path: inputs.path, createdAt: serverTimestamp() }); resolve("New document inserted successfully!"); } catch (e) { // Handle error } }); } readDocs: (...args) => { const [collection_name] = args; let docs = []; const ref = collection(db, collection_name); return new Promise(async resolve => { try { const snapshots = await getDocs(ref); snapshots.forEach(doc => { const d = { ...doc.data() }; docs.push(d); }); resolve(docs); } catch (e) { console.log(e); } }); } ``` ### Step 4: Perform Database Operations ```javascript readDocs("stocks").then(console.log); writeDoc(inputs, "stocks").then(console.log);` ``` ## .env Variables To securely store sensitive information or configuration settings in your React.js app, you can use environment variables with a `.env` file. Here's how you can set it up: 1. Create a `.env` file in the root directory of your project. 2. Add the `.env` file to your `.gitignore` file to ensure it's not committed to version control, keeping your sensitive information secure. In your `.env` file, define your environment variables with the `REACT_APP_` prefix. For example: `REACT_APP_API_KEY=veryImportantInfo` To access these static variables within your React components, you can use `process.env.REACT_APP_` followed by the variable name. For instance, to access the API key defined above, you would use: `const apiKey = process.env.REACT_APP_API_KEY; // veryImportantInfo` ### Hints - Remember that the `REACT_APP_` prefix is required for the environment variables to be recognized in your React app. - By using environment variables and the `.env` file, you can keep sensitive information separate from your codebase and easily manage different configurations for different environments (e.g., development, production). - Please note that you'll need to restart your development server (e.g., using `npm start`) for the changes in the `.env` file to take effect. ## Cloud Storage To learn how to implement Storage (provided by Google Firebase) in your React.js app, follow these steps: ### Step 1 Go to Storage page, set up, and choose stored location ### Step 2 ```javascript const storage = getStorage(app); uploadFile: (media) => { return new Promise(async resolve => { try { const storageRef = ref(storage, `images/${media.title}`); // Second argument represents the path in Cloud Storage uploadBytes(storageRef, media.file /* Use inputs.file to upload */).then(snapshot => { resolve({ path: snapshot.metadata.fullPath, name: media.title }); }); } catch (e) { console.error(e); } }); } downloadFile: (media) => { return new Promise(async resolve => { try { const storageRef = ref(storage, media.path); const fileURL = await getDownloadURL(storageRef); // Get URL from the same ref resolve(fileURL); } catch (e) { console.error(e); } }); } ``` ### Step 3 ```javascript uploadFile(state.inputs) .then(downloadFile) .then(url => { writeDoc({ ...inputs, path: url }, "stocks").then(() => { dispatch({ type: 'setItem' }) dispatch({ type: 'collapse', payload: { bool: false } }) }); });` ``` Update from local temporary store to cloud storage, overwrite the path with the URL sent back from the upload/download process. #### Timestamp We use `setDoc(docRef, {...inputs, createdAt: serverTimestamp()});` to trace update timing. In local variables, we can use: const timestamp = 1. `${new Date(createdAt.seconds*1000)}` Fri May 26 2023 16:03:49 GMT+0800 (台北標準時間) 2. const date = `${new Date(createdAt.seconds * 1000)}`.split(" ") `${date[1]} ${date[2]} ${date[3]}` May 26 2023 Use `{timestamp}` in the component to represent date. ## Authentication **Example: Google Sign-in** ### Step 1 - Enable authentication in the Firebase console. Google will automatically set up the Web SDK for the user. - Alternatively, go to the Google Cloud Platform, link your project, and manually add credentials. Make sure to add the Authorized JavaScript origins and Authorized redirect URIs. ### Step 2 Implement the following snippet: ```javascript export const auth = getAuth(app); signIn: () => { return new Promise(resolve => { signInWithPopup(auth, provider).then(response => { resolve(response.user); }).catch(console.error); }); } signOut: () => { return new Promise(resolve => { signOut(auth).then(() => { console.log("User logged out"); resolve(); }).catch(console.error); }); } ``` Add these methods with `currentUser` to keep track on the top level (`Context.Provider`): ```javascript import FirebaseAuth from "../handlers/auth"; const { signIn, signOut } = FirebaseAuth; const [currentUser, setCurrentUser] = useState(null); const login = () => signIn().then(res => setCurrentUser(res)); const logout = () => signOut().then(() => setCurrentUser(null)); ``` ### Step 3 Access `currentUser` and apply the login/logout function in the target component: ```javascript // Login button const LogIn = () => { const { login, currentUser } = useAuthContext(); return ( <button type="button" className="btn" onClick={login}> Login </button> ); }; ``` ### Bonus ```javascript getCurrentUser: () => { return new Promise(resolve => { return auth.onAuthStateChanged(resolve); }); }; const authenticate = () => getCurrentUser().then(setCurrentUser); ``` Firebase also offer special method for stateful process, call the `authenticate` function to remember the login status. ## React Rounter Basic ### Router, Routes, and Route **Prerequisites**: To use React Router, you need to install the `react-router-dom` package. You can do this by running the following command: `npm install react-router-dom` Here is a simple demonstration of React Router's usage: ```javascript import React from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import Home from './components/Home'; import About from './components/About'; import NotFound from './components/NotFound'; export default function App() { return ( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="*" element={<NotFound />} /> </Routes> </Router> ); }` ``` In this example: - The `Router` component from `react-router-dom` is used as the top-level component to wrap the routes. - Inside the `Router`, the `Routes` component is used to define the routing logic. - Each individual route is defined using the `Route` component. The `path` prop specifies the URL path, and the `element` prop specifies the component to be rendered when the URL matches the path. - In this example, we have three routes: "/" for the home page, "/about" for the about page, and "*" for any other unknown routes (handled by the `NotFound` component). ### Link The `Link` component in `react-router-dom` is used to replace the native `<a>` tag for declarative navigation within your application. It prevents full page refresh and utilizes client-side routing to update the URL and render the appropriate components without reloading the entire page. Here is an example of using the `Link` component for navigation: ```javascript import React from 'react'; import { Link } from 'react-router-dom'; export default function Navigation() { return ( <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> </ul> </nav> ); } ``` The `Link` component accepts a `to` prop, which specifies the target URL or route path to navigate to. When a `Link` is clicked, it triggers the routing mechanism to render the corresponding route component and update the URL in the address bar. By using React Router and the `Link` component, you can easily create a navigational structure for your application and handle routing with a smooth user experience. ## React Router Hooks ### useNavigate The `useNavigate` hook in `react-router-dom` allows you to programmatically navigate to different routes within your application. It provides a function that you can call to trigger navigation changes. Here's an example: ```javascript import React from 'react'; import { useNavigate } from 'react-router-dom'; export default function MyComponent() { const navigate = useNavigate(); const handleClick = () => { navigate('/about'); // Navigate to the '/about' route }; return ( <div> <button onClick={handleClick}>Go to About</button> </div> ); } ``` In this example, the `useNavigate` hook is used to obtain the `navigate` function. When the button is clicked, the `navigate` function is called with the desired route as its argument, triggering the navigation to the specified route. You can also pass data along with the navigation using the `state` option: ```javascript import React from 'react'; import { useNavigate } from 'react-router-dom'; export default function MyComponent() { const navigate = useNavigate(); const handleClick = () => { const data = { id: 123, name: 'John Doe' }; navigate('/about', { state: data }); // Pass data along with the navigation }; return ( <div> <button onClick={handleClick}>Go to About</button> </div> ); } ``` In this case, an object `data` is passed along with the navigation to the `/about` route. This data can be accessed in the target component using the `useLocation` hook. ### useLocation The `useLocation` hook allows you to access the current location object in a functional component. The location object contains information about the current URL, including any state data passed during navigation. Here's an example of how you can use the `useLocation` hook to access the passed data: ```javascript import React from 'react'; import { useLocation } from 'react-router-dom'; export default function About() { const location = useLocation(); const data = location.state; console.log(data.id); // Output: 123 console.log(data.name); // Output: John Doe return ( <div> <h2>About Page</h2> {/* Render the rest of the component */} </div> ); } ``` In this example, the `useLocation` hook is used to obtain the `location` object. The `state` property of the `location` object contains the data that was passed during navigation. By accessing `location.state`, you can retrieve the passed data and use it within the component. It's important to note that the `state` property is only available if data was passed during navigation. Therefore, it's a good practice to check if the data exists before accessing its properties to avoid potential errors.