# WEEK 5 - Router and Context ###### tags: `WEB` `JS` `DIGITALIZE` `FRONTEND` `REACTJS` ## Component Lifecycle Each component has several lifecycle methods that you can override to run code at particular times in the process. ![](https://i.imgur.com/LebDbHf.png) * **Mounting** Mounting means putting elements into the DOM. React has four built-in methods that gets called, in this order, when mounting a component: 1. constructor() 2. getDerivedStateFromProps() 3. render() 4. componentDidMount() The render() method is required and will always be called, the others are optional and will be called if you define them. Example: ``` import React, { Component } from 'react'; class ShowHide extends Component { constructor(props) { super(props)// props should be passed to superclass this.state = { // init state showMsg: false } this.showHide = this.showHide.bind(this) // bninding function to this class } // this executed after constructor and before render functions static getDerivedStateFromProps(props, state) { console.log(props) // old one console.log(state) // old one return { // return new values of the state showMsg:true } } showHide() { this.setState({ showMsg: !this.state.showMsg }) } render() { console.log('rendering after getDerivedStateFromProps function', this.state) let { showMsg } = this.state return <div> <button onClick={this.showHide}>Show/Hide</button> {showMsg && <h1>Hello, there</h1>} </div> } } export default ShowHide; ``` * **Updating** The next phase in the lifecycle is when a component is updated. A component is updated whenever there is a change in the component's state or props. React has five built-in methods that gets called, in this order, when a component is updated: 1. getDerivedStateFromProps() 2. shouldComponentUpdate() 3. render() 4. getSnapshotBeforeUpdate() 5. componentDidUpdate() The render() method is required and will always be called, the others are optional and will be called if you define them. ``` import React, { Component } from 'react'; class ShowHide extends Component { constructor(props) { super(props)// props should be passed to superclass this.state = { // init state showMsg: false } this.showHide = this.showHide.bind(this) // bninding function to this class console.log('constructor') } // this function added to understand how component will not changed on update! shouldComponentUpdate() { return false; } // this executed after constructor and before render functions static getDerivedStateFromProps(props, state) { console.log('getDerivedStateFromProps') console.log(props) // old one console.log(state) // old one return { // return new values of the state showMsg:true } } showHide() { this.setState({ showMsg: !this.state.showMsg }) } render() { console.log('render') console.log('rendering after getDerivedStateFromProps function', this.state) let { showMsg } = this.state return <div> <button onClick={this.showHide}>Show/Hide</button> {showMsg && <h1>Hello, there</h1>} </div> } componentDidMount() { console.log('componentDidMount') console.log("Executed after render function and after component amounted to the DOM") console.log('Here you can make heavy & side effetcs tasks') } } export default ShowHide; ``` * **Unmounting** The next phase in the lifecycle is when a component is removed from the DOM, or unmounting as React likes to call it. React has only one built-in method that gets called when a component is unmounted: 1. componentWillUnmount() ## ReactJS Hooks Hooks are the new feature introduced in the React 16.8 version. It allows you to use state and other React features without writing a class. Hooks are the functions which "hook into" React state and lifecycle features from function components. It does not work inside classes. We can simply imagine as it the migration from class component to functional and make the latest one to be used as statefull component with additional capabilities and features. ### useState() There is no constructor function and instead of that we can use useState() function inside the function component to create and manage states of the component. Example: ``` import React, { useState } from 'react'; function CountApp() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } export default CountApp; ``` ### useEffect() The Effect Hook allows us to perform side effects (an action) in the function components. It does not use components lifecycle methods which are available in class components. In other words, Effects Hooks are equivalent to componentDidMount(), componentDidUpdate(), and componentWillUnmount() lifecycle methods. Side effects have common features which the most web applications need to perform, such as: * Updating the DOM, * Fetching and consuming data from a server API, * Setting up a subscription, etc. Example: ``` import React, { useState, useEffect } from 'react'; function CounterExample() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } export default CounterExample; ``` **Others important built-in hooks:** useContext useReducer useCallback useMemo useRef ## SPA & Routing SPA stands for Single Page Application. It is a very common way of programming websites these days. The idea is that the website loads all the HTML/JS the first time you visit. When you then navigate, the browser will only rerender the content without refreshing the website. ### React-Router In traditional websites, the browser requests a document from a web server, downloads and evaluates CSS and JavaScript assets, and renders the HTML sent from the server. When the user clicks a link, it starts the process all over again for a new page. Client side routing allows your app to update the URL from a link click without making another request for another document from the server. Instead, your app can immediately render some new UI and make data requests with fetch to update the page with new information. **React-router-dom package** enables faster user experiences because the browser doesn't need to request an entirely new document or re-evaluate CSS and JavaScript assets for the next page. It also enables more dynamic user experiences with things like animation. - Install dependencies https://www.npmjs.com/package/react-router-dom ``` npm install react-router-dom ``` Visualization: https://remix.run/_docs/routing **Let's a odd react-router to our application `clinic`** - First, let's install the package `npm i react-router-dom` - Add the following code to `index.js`: ``` import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import Patients from './components/FPatients'; import History from './pages/history'; import { createBrowserRouter, RouterProvider, Route } from 'react-router-dom' import Layout from './pages/layout'; const router = createBrowserRouter([ { path:'/', element:<Layout/>, children:[ { path:'patients', element:<Patients/> }, { path:'history', element:<History/> } ] } ]); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <RouterProvider router={router}/> </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); ``` We want the patients, history pages to render inside of the <App> layout. so we need layout components that will stay shown in each nested component, for example 'Sidebar'. - create `layout` at `pages/layout.js`: ``` import { Outlet ,Link} from 'react-router-dom'; export default function Layout() { return ( <> <div id="sidebar"> <nav> <ul> <li> <Link to={'patients'}>Patients</Link> </li> <li> <Link to={'history'}>History</Link> </li> </ul> </nav> </div> <div id="detail"> <Outlet/> </div> </> ); } ``` to define our pages, we have to create routes array and pass it to the `createBrowserRouter`, each object of this array at least must have the following properties: `path`, `element`. - Let's create our pages `History`, `Patients` at `src/pages/`: `patients` ``` import { useState } from "react" import PCard from "./PatientCard"; const PATIENTS = [ { id:1, full_name:"Ali Ahmed", birth_date:"10/10/1999", gender:"m", phone:"+96477889654" }, { id:2, full_name:"Ameer Saad", birth_date:"10/10/2000", gender:"m", phone:"+96477809654" }, { id:3, full_name:"Muna Ali", birth_date:"10/10/1998", gender:"f", phone:"+964777809654" } ]; const Patients = (props)=>{ const [patients, setPatients] = useState(PATIENTS) const [fullName , setFullName] = useState('') const [gender, setGender] = useState('m') const addNew = ()=>{ if(!fullName) { alert('Please fill your name') return; } let pTemp = !patients?[]:patients pTemp.push({ id:4, full_name:fullName, birth_date:"10/10/1999", gender:gender, phone:"+964777809604" }) setPatients([...pTemp]) } const changeFullName = (e)=>{ let v = e.target.value console.log(v) setFullName(v) } const genderOnChange = (e)=>{ setGender(e.target.value) } const submit = (e)=>{ e.preventDefault() addNew() } return <div> <form onSubmit={submit}> <input value={fullName} onChange={changeFullName} type="text" placeholder="Full Name"/> <select value={gender} onChange={genderOnChange}> <option value='m'>Male</option> <option value="f">Female</option> </select> <input type="submit"/> </form> { patients?patients.map((item,index)=>{ return <PCard key={index} data={item}/> }) : <p>No Data</p> } </div> } export default Patients; ``` `history` ``` const History = ()=>{ return <p>History Page</p> } export default History; ``` Now run the application should start and you can navigate from page to another inside the layout. - Let us add Active link styling: Now that we have a bunch of records, it is not clear which one we are looking at in the sidebar. We can use `NavLink` to fix this. ``` <NavLink className={({ isActive, isPending }) => isActive ? "active" : isPending ? "pending" : "" } to={'patients'}>Patients</NavLink> </li> <li> <NavLink ``` - Let us add new page to view a patient details, we need to pass id of the patient as parameter in the URL to this page, first we need to define a new route object in routes array: ``` { path:"patients/:id", element:<Patient/> } ``` as you see, we use this pattern `/:id`, colon to inform the router that there will be a parameter passing that destination page have to receive. `pages/patient.js` ``` import { useParams } from "react-router-dom" const Patient= ()=>{ const pars = useParams() return <p>Hi, {pars.id}</p> } export default Patient; ``` to access passed parameter via URL, we can use `useParams` hooks function. Now we can test our new changes and it should work as expected. ## Component Composition Component composition is the name for passing components as props to other components, thus creating new components with other components. Example: Define Button component: ``` const Button = ({ onClick, children }) => ( <button onClick={onClick}>{children}</button> ); ``` Use Button Component as wrapper for other children: ``` const App = () => { const onClick = () => alert('Hey 👋'); return ( <Button onClick={onClick}> <img src="/logos/logo.svg" /> Click me! </Button> ); }; ``` Composition help us to solve prop drilling problem and enhance the performance. **Prop Drilliing:** Prop drilling is the act of passing props through multiple layers of components. ## Context Context provides a way to pass data through the component tree without having to pass props down manually at every level. In a typical React application, data is passed top-down (parent to child) via props, but such usage can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree. Example: Pass theme mode as props through 3 layers so ButtonThemed can use it ``` class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { // The Toolbar component must take an extra "theme" prop // and pass it to the ThemedButton. This can become painful // if every single button in the app needs to know the theme // because it would have to be passed through all components. return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } } ``` Using context, we can avoid passing props through intermediate elements: ``` // Context lets us pass a value deep into the component tree // without explicitly threading it through every component. // Create a context for the current theme (with "light" as the default). const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // Use a Provider to pass the current theme to the tree below. // Any component can read it, no matter how deep it is. // In this example, we're passing "dark" as the current value. return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // A component in the middle doesn't have to // pass the theme down explicitly anymore. function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // Assign a contextType to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". static contextType = ThemeContext; render() { return <Button theme={this.context} />; } } ``` ### API **React.createContext** `const MyContext = React.createContext(defaultValue);` **Context.Provider** `<MyContext.Provider value={/* some value */}>` Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes. The Provider component accepts a value prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree. All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. **Context.Consumer** Children components (subscribed) act as consumer, to access context provided by the provider: Class Component ``` <MyContext.Consumer> {value => /* render something based on the context value */} </MyContext.Consumer> ``` Function Component: ``` const context = React.useContext(MyContext); ``` ### Theme provider for Clinic App Let's add functionality of Theme switching using Context API. - Create new directory called `contexts` - Create sub-directory called `ThemeContext.js` - Write context provider code: ``` import {createContext} from 'react' export const THEMES = { dark:{ background:'black' }, light:{ background:'blue' } } const ThemeContext = createContext(THEMES.light) const ThemeProvider = ThemeContext.Provider; export {ThemeContext,ThemeProvider}; ``` Access context from the consumers components: ``` import { ThemeContext } from "../contexts/ThemeContext/themeContext"; ... const context = useContext(ThemeContext) ... <input style={{ backgroundColor:context.background }} type="submit"/> ``` Now apply the context provider: ``` root.render( <React.StrictMode> <ThemeProvider value={THEMES.light}> <RouterProvider router={router}/> </ThemeProvider> </React.StrictMode> ); ``` Now you can print the context object at your console to see that consumers access the values provided from the provider and shared easily without passing props from parent to deep child. But what if we want to manage the state with the provider? let's optimize the the code design: let's create a state `themeValue` inside the provider: ``` import {createContext,useState} from 'react' export const THEMES = { dark:{ background:'black' }, light:{ background:'blue' } } const defaultSettings = { theme:THEMES.light } const ThemeContext = createContext(THEMES.light) export const ThemeProvider = ({theme, children})=>{ console.log('default passed',theme) const [themeValue, setThemeValue] = useState(theme|defaultSettings) return ( <ThemeContext.Provider value={{ settings:themeValue }}> {children} </ThemeContext.Provider> ) }; //const ThemeProvider = ThemeContext.Provider; export default ThemeContext; ``` Wrap our application with provider: ``` import {THEMES} from './contexts/ThemeContext/themeContext' root.render( <React.StrictMode> <ThemeProvider theme={THEMES.light}> <RouterProvider router={router}/> </ThemeProvider> </React.StrictMode> ); ``` Now we can still access the state inside any child component like `patients.js`: ``` import ThemeContext from "../contexts/ThemeContext/themeContext"; const context = useContext(ThemeContext) const backgroundColor = context.background; ... <input style={{ backgroundColor:backgroundColor }} type="submit"/> ``` Good!, Let's add new component to toggle the theme and apply some css on the component so we can see how our application will work:\ First let's apply some css on the components: - Sidebar: create a sidebar component in `components` directory and create a new file called `sidebar.jsx` ``` import { NavLink } from "react-router-dom/dist/umd/react-router-dom.development" export const Sidebar = ()=>{ return (<div className="flex flex-col h-screen p-3 bg-white shadow w-60"> <div className="space-y-3"> <div className="flex items-center"> <h2 className="text-xl font-bold">Dashboard</h2> </div> <div className="flex-1"> <ul className="pt-2 pb-4 space-y-1 text-sm"> <li className="rounded-sm h-8"> <NavLink className={({isActive, isPending})=>{ let st = isActive?"active":isPending?"pending":""; return st+' '+'p-1 rounded-lg'; }} to={'patients'}> Patients </NavLink> </li> <li className="rounded-sm h-8"> <NavLink className={({isActive, isPending})=>{ let st = isActive?"active":isPending?"pending":""; return st+' '+'p-1 rounded-lg'; }} to={'history'}> History </NavLink> </li> </ul> </div> </div> </div>) } ``` - `App.css` ``` .bg-pink{ background-color: pink; } .active{ background-color: #61dafb; } ``` - `Layout.js` ``` import { Outlet } from "react-router-dom/dist/umd/react-router-dom.development"; import { Sidebar } from "../components/Sidebar"; export default function Layout(){ return <> <header className='fixed top-0 left-0 right-0 h-12 bg-red-300 m-0'> </header> <div className="flex flex-row mt-12"> <Sidebar/> <div id="detiails" className="w-full p-8"> <Outlet/> </div> </div> </> } ``` - Let's add switch component to enable user switch theme mode: we will install a package `react-switch`, to make our practicing easy: ``` npm i react-switch ``` create `switcher.jsx` component at `components` and add the following: ``` import ThemeContext from '../contexts/ThemeContext/themeContext' import {useContext} from 'react' import Switch from 'react-switch' export const Switcher = ()=>{ const context = useContext(ThemeContext) console.log('c in switcher',context) return <label> <span>{context.settings.themeValue.name}</span> <Switch onChange={context.actions.onSwitcherChange} checked={context.settings.checked} /> </label> } ``` As you see displayed value changable depends on selected theme, also to toggle switch component we have to manage a boolean state value. Let's (add states, edit THEMES object, define call back action for switch change event, and expose all these to consumers) to the ThemeProvider context so our switch component can access: ``` import {createContext,useState} from 'react' export const THEMES = { dark:{ name:"Dark", background:'bg-zinc-900 ' }, light:{ name:"Light", background:'bg-sky-400' } } const defaultSettings = { theme:THEMES.light } const ThemeContext = createContext(THEMES.light) export const ThemeProvider = ({theme, children})=>{ const [themeValue, setThemeValue] = useState(theme) const [checked, setChecked] = useState(theme.name=='Light'?true:false)// true light, false is dark console.log('default passed',theme) console.log('set to ',themeValue) const onSwitcherChange = (e)=>{ console.log(e) setChecked(!checked) setThemeValue(e?THEMES.light:THEMES.dark) console.log(themeValue) } return ( <ThemeContext.Provider value={{ settings:{ themeValue, checked }, actions:{ onSwitcherChange } }}> {children} </ThemeContext.Provider> ) }; //const ThemeProvider = ThemeContext.Provider; export default ThemeContext; ``` `onSwitcherChange` listen to change event on switch component and update the state so render function rendering again with new values passing to the consumers. Also let's change background color of the header and sidebar, so they are changing depends on selected theme: `sidebar.jsx`: ``` import { useContext } from "react" import { NavLink } from "react-router-dom/dist/umd/react-router-dom.development" import ThemeContext from "../contexts/ThemeContext/themeContext" export const Sidebar = ()=>{ const context = useContext(ThemeContext) let color = context.settings.themeValue.background return (<div className={`flex flex-col h-screen p-3 shadow w-60 ${color}`}> <div className="space-y-3"> <div className="flex items-center"> <h2 className="text-xl font-bold">Dashboard</h2> </div> <div className="flex-1"> <ul className="pt-2 pb-4 space-y-1 text-sm"> <li className="rounded-sm h-8"> <NavLink className={({isActive, isPending})=>{ let st = isActive?"active":isPending?"pending":""; return st+' '+'p-1 rounded-lg'; }} to={'patients'}> Patients </NavLink> </li> <li className="rounded-sm h-8"> <NavLink className={({isActive, isPending})=>{ let st = isActive?"active":isPending?"pending":""; return st+' '+'p-1 rounded-lg'; }} to={'history'}> History </NavLink> </li> </ul> </div> </div> </div>) } ``` `layout.jsx` ``` import { Outlet } from "react-router-dom/dist/umd/react-router-dom.development"; import { Sidebar } from "../components/Sidebar"; import { Switcher } from "../components/switcher"; import ThemeContext from "../contexts/ThemeContext/themeContext"; import {useContext} from 'react' export default function Layout(){ const context = useContext(ThemeContext) const color = context.settings.themeValue.background; console.log('color', color) return <> <header className={`fixed top-0 left-0 right-0 h-12 m-0 ${color}`}> <Switcher/> </header> <div className="flex flex-row mt-12"> <Sidebar/> <div id="detiails" className="w-full p-8"> <Outlet/> </div> </div> </> } ``` Now as you see, you can toggle the theme from the switch and sidebar and header background will change according to the updates on provider state. ## Tailwind CSS Tailwind CSS, as per their own website is a "utility-first CSS framework" which provides several of these opinionated, single-purpose utility classes that you can use directly inside your markup to design an element. ### Installation It is prefer to follow their official steps: https://tailwindcss.com/docs/guides/create-react-app Help: - Append command `postcss src/App.css -o public/output.css` to the script in package.json file: ``` "scripts": { "start": "react-scripts start && postcss src/App.css -o public/output.css", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, ``` and in the `public/index.html`, link output.css: ``` <link href="%PUBLIC_URL%output.css" rel="stylesheet"> ``` ## Task ## References https://reactrouter.com/ https://tailwindcss.com/ https://reactjs.org/docs/ https://www.npmjs.com/package/react-switch