# 10 - ReactJS Advanced
## Context API
As our application grows, these components can become huge and unmaintainable, so we break them into smaller components.
For reusability purposes, You have to break your application into smaller components.
After breaking components into smaller components for maintainability purposes, these small components might now need some data to work properly. If these small components need data to work with, you will have to pass data through props from the parent component to the child component. This is where we can slow down our application and cause development issues.
Example
```
const Notes = () => {
const [notes] = useState([
{
title: "First note",
description: "This is my first note",
done: false
}
]);
return (
<div>
<h1>Notes</h1>
{notes.map(note => {
return (
<div>
<h1>{note.title}</h1>
<h3>{note.description}</h3>
<p>{note.done ? "done!" : "not done!"}</p>
</div>
);
})}
</div>
);
};
```
The above component need to be more re-usable , let's optimize it's code design:
```
const Notes = () => {
const [notes] = useState([
{
title: "First note",
description: "This is my first note",
done: false
}
]);
return (
<div>
<h1>Notes</h1>
{notes.map(({ title, description, done }) => {
return <Note title={title} description={description} done={done} />;
})}
</div>
);
};
const Note = ({ title, description, done }) => {
return (
<div>
<Title title={title} />
<Description description={description} />
<Done done={done} />
</div>
);
};
const Title = ({ title }) => {
return <h1>{title}</h1>;
};
const Description = ({ description }) => {
return <h3>{description}</h3>;
};
const Description = ({ description }) => {
return <h3>{description}</h3>;
};
```
Above example grows in size and we feel the need to break these components into smaller components, we might have a problem.
**Impact**
- Passing data through props over and over can cause problems for your application.
- Sometimes you might pass more props than you need or even forget to pass props that you do need, rename props through the components without noticing, etc.
- Leading to **"prop-drilling."**
Finally, That’s one of the main problems that Context API came to solve for us.
**So now again, What is Context API?**
Simply, The Context API can be used to share data with multiple components, without having to pass data through props manually.
### createContext
To start with the Context API, the first thing we need to do is create a context using the createContext function from React.
```
const NotesContext = createContext([]);
```
Each context has two React components that are going to be used: Provider and Consumer.
**Provider**
The Provider component is going to be used to wrap the components that are going to have access to our context.
```
<NotesContext.Provider value={this.state.notes}>
...
</Notes.Provider>
```
The Provider component receives a prop called value, which can be accessed from all the components that are wrapped inside Provider, and it will be responsible to grant access to the context data.
**Consumer**
The Consumer component allows a React component to subscribe to the context changes. The component makes the data available using a render prop.
```
<NotesContext.Consumer>
{
values => <h1>{value</h1>
}
</Notes.Consumer>
```
**Example:**
First, Create a context file named "NotesContext.js", and write the following code:
```
import { createContext } from "react";
let NotesContext = createContext([])
export default NotesContext;
```
Create NotesProvider that will be the wrapper for other components:
```
import React, { useState } from 'react';
import NotesContext from '../contexts/NotesContext'
function NotesProvider(props) {
const [notes,setNotes] = useState([
{
id:1,
title:'Note 1',
},
{
id:2,
title:'Note 2',
},
{
id:3,
title:'Note 3',
}
])
return (
<NotesContext.Provider
value={{
notes:notes
}}
>
{props.children}
</NotesContext.Provider>
);
}
export default NotesProvider;
```
Now, lets write a consumer component that must have access to the shared data "notes":
```
import React, { Fragment } from 'react';
import NotesContext from '../contexts/NotesContext'
import Note from '../optimized/Note'
function Notes(props) {
console.log('notes:',props)
return (
<NotesContext.Consumer>
{
ctx=>(
<Fragment>
{
ctx.notes.map((note)=>{
return <Note key={note.id} object={note}/>
})
}
</Fragment>
)
}
</NotesContext.Consumer>
);
}
export default Notes;
```
Finally, usage of provider and consumer should looks like the following:
```
import './App.css';
import NotesProvider from './components/optimized/NotesProvider'
import Notes from './components/optimized/Notes'
function App() {
return (
<div className="App">
<NotesProvider>
<Notes/>
</NotesProvider>
</div>
);
}
export default App;
```
### useContext
The useContext hook allows us to connect and consume a context. The useContext hook receives a single argument, which is the context that you want to have access to.
```
const notes = useContext(NotesContext);
```
The useContext is way better and cleaner than the Consumer component—we can easily understand what’s going on and increase the maintainability of our application.
Example:
We will refactor previous example for learning how to use useContext.
First, let's create the context again:
```
import { createContext, useContext,useState } from "react";
let NotesContext = createContext([])
export const useNotes = ()=>{
const cont = useContext(NotesContext)
if(cont == undefined)
throw new Error("useAuth should be used inside the provider");
return cont;
}
export const NotesProvider = ({ children }) => {
const [notes, setNotes] = useState([
{
id:1,
title:'Note 1',
},
{
id:2,
title:'Note 2',
},
{
id:3,
title:'Note 3',
}
]);
const handleNotes = (newData) => {
setNotes(newData);
};
const data = [notes, handleNotes];
return <NotesContext.Provider value={data}>{children} </NotesContext.Provider>;
};
export default NotesContext;
```
if you noticed, the provider declared and implemented inside context, also there is a new function added called **"useNotes"**, this function will be used to give us access to the context of notes.
Now, since we are using `useContext` instead of Consumer, let's update on the consumer component:
```
import React, { Fragment } from 'react';
import Note from './Note'
import {useNotes} from '../contexts/NotesContext'
function Notes(props) {
const [notes,setNotes] = useNotes()
console.log('notes:',props)
const addNewOne = ()=>{
let temp = notes;
temp.push({id:6,title:'Hi'})
setNotes([...temp])
}
return <div>
{
notes.map((note)=>{
return <Note key={note.id} object={note}/>
})
}
<button onClick={addNewOne}>Empty notes</button>
</div>
}
export default Notes;
```
we used "useNotes" instead of wrapping components with `Context.Consumer`.
Now at this point, the child component has full access to the context and can call exported functios as well.
App.js looks like:
```
import './App.css';
import {NotesProvider} from './components/contexts/NotesContext'
import Notes from './components/optimized/NotesU'
function App() {
return (
<div className="App">
<NotesProvider>
<Notes/>
</NotesProvider>
</div>
);
}
export default App;
```
Pay attention to the importing part of the provider, this time will import the provider from the context itself in addition we imported it as Importing named values.
## More of ReactJS Hooks
### UseReducer
The useReducer() hook in React lets you separate the state management from the rendering logic of the component.
The useReducer(reducer, initialState) hook accept 2 arguments: the reducer function and the initial state. The hook then returns an array of 2 items: the current state and the dispatch function.
```
const [state, dispatch] = useReducer(reducer, initialState);
```
1. Initial state
The initial state is the value the state is initialized with.
```
// initial state
const initialState = {
counter: 0
};
```
2. Action object
An action object is an object that describes how to update the state.
```
const action = {
type: 'command'
};
```
If the action object must carry some useful information (aka payload) to be used by the reducer, then you can add additional properties to the action object.
For example, here's an action object to add a new user to an array of users state:
```
const action = {
type: 'add',
user: {
name: 'Mohammed',
email: 'mohammed@mail.com'
}
};
```
3. Dispatch function
The dispatch is a special function that dispatches an action object.
```
const [state, dispatch] = useReducer(reducer, initialState);
```
Usage: whenever you want to change the state, you call `dispatch` function with passing action object.
4. Reducer function
The reducer is a pure function that accepts 2 parameters: the current state and an action object. Depending on the action object, the reducer function must update the state in an immutable manner, and return the new state.
Example:
```
function reducer(state, action) {
let newState;
switch (action.type) {
case 'increase':
newState = { counter: state.counter + 1 };
break;
case 'descrease':
newState = { counter: state.counter - 1 };
break;
default:
throw new Error();
}
return newState;
}
```
Full Example repo: (Auth)
## MUI customization
There are 3 possible APIs you can use to generate and apply styles, however they all share the same underlying logic.
* Hook API
Using makeStyles() hook function
Example:
```
import * as React from 'react';
import { makeStyles } from '@mui/styles';
import Button from '@mui/material/Button';
const useStyles = makeStyles({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
});
export default function Hook() {
const classes = useStyles();
return <Button className={classes.root}>Styled with Hook API</Button>;
}
```
* Styled components API
Example:
```
import * as React from 'react';
import { styled } from '@mui/styles';
import Button from '@mui/material/Button';
const MyButton = styled(Button)({
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
});
export default function StyledComponents() {
return <MyButton>Styled with styled-components API</MyButton>;
}
```
* Higher-order component API
```
import * as React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@mui/styles';
import Button from '@mui/material/Button';
const styles = {
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
};
function UnstyledComponent(props) {
const { classes } = props;
return <Button className={classes.root}>Styled with HOC API</Button>;
}
UnstyledComponent.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(UnstyledComponent);
```
* Using the theme context
```
import { makeStyles } from '@mui/styles';
import { createTheme, ThemeProvider } from '@mui/material/styles';
const theme = createTheme();
const useStyles = makeStyles((theme) => ({
root: {
color: theme.palette.primary.main,
}
}));
const App = (props) => {
const classes = useStyles();
return <ThemeProvider theme={theme}><div {...props} className={classes.root}></ThemeProvider>;
}
```
## UseRef
The useRef is a hook that allows to directly create a reference to the DOM element in the functional component.
```
const refContainer = useRef(initialValue);
```
The useRef returns a mutable ref object. This object has a property called .current. The value is persisted in the refContainer.current property. These values are accessed from the current property of the returned object. The .current property could be initialised to the passed argument initialValue e.g. useRef(initialValue). The object can persist a value for a full lifetime of the component.
**Accessing DOM elements**
1. Define the reference to access the element
` const elementRef = useRef();`
2. Assign the reference to ref attribute of the element
`<div ref={elementRef}></div>;`
3. After mounting, elementRef.current points to the DOM element.
Example:
```
import { useRef, useEffect } from 'react';
function AccessingElement() {
const elementRef = useRef();
useEffect(() => {
const divElement = elementRef.current;
console.log(divElement); // logs <div>I'm an element</div>
}, []);
return (
<div ref={elementRef}>
I'm an element
</div>
);
}
```
for example, if you want to change style of component useing ref, you can use current object value
```
elementRef.current.style.backgroundColor = "blue";
```
let's try it on [sandbox](https://codesandbox.io/)
## Practicing
In this section, we will start working on advanced UI connected to a backend API, we will use "Computiq board API" for learning and practicing purposes only.
**Idea**
Re-implement UI/UX of students-board page of Computiq website

But, we need to try add some new features like:
* Search in score board
* Theme switcher (Light, Dark)
* Add animation & transition
### Get Started
**Projet Setup**
1. Create CRA project
```
npx create-react-app computiq-x
```
2. Struct the project
Let's take a look at the final tree of the application.
* components
* Login
* guards
* AuthGuard
* GuestGuard
* LoadingScreen.jsx
* contexts
* AuthContext
* SettingsContext
* hooks
* useAuth
* useSettings
* views
* LoginView.jsx
* BoardView.jsx
* NotFoundView.jsx
* utils
* classes
* BaseHttp.js
* HttpHelper.js
* axios.js
* responseHandler.js
* common
* constants.js
* layouts
* DashboardLayout
* NavBar
* TopBar
* index.js
* services
* Http
* LoginService
* routes.js
Take a look at the following diagram to understand how above tree will be implemented and what should it's component be.

**Repo:**
https://github.com/ProgFadi/computiq-x.git
**What's new in the project?**
* Define routes as an array and render them as routes.
Main reason for doing that is to protect each route in more creative way.
* old react-router-dom used
Reason: for availability of render props for each route that helps us to wrap component with Guard and Layout.
* New Context added (SettingsContext)
To manage settings states like (theme)
* Main layout
Used to be the container and wrapper for application, has TopBar and NavBar shared components among all the pages and other components.
* Re-usable Http Helper
a new instance of axios initialized in seperated file, them implemented in a seperate class that contains CRUD requests schema.
* Response handler function
Implemeneted in seperat JS file to be used for handling error of Http requests everywhere in the application.
* services directory and files
Each file of them used to be final layer to make request to the server using Http Helper, handle requests & the place to write BLL.
* Guards
Components used as wrapper for each route, implement useAuth hook function to check if user authorized or not, based on that it will decide where can user navigate.
* New packages used
1. jwt-decode: used in AuthContext to decode JWT, check expiration of it.
2. loadash: simplify programming with strings, numbers, arrays, functions and objects.
3. notistack: for stacking notifications.
### Customization
**Login UI**
- Form Validation
- Add response handler & flash a message using snackbar.
- Disable button while logging.
**Settings - Theme Switching**
- Review theme schema of MUI
- Review SettingsContext code.
- Customize light and dark theme using online MUI tool.
- Create Themes file.
- Prepare application for implementing Settings and support RTL https://mui.com/guides/right-to-left/
- Implement Theme switcher via settings component.
**Board UI**
- Remove NavBar
- Apply Blue effect on TopBar
- Design responsive layout
- Implement UI of points & weeks
Computiq API:
https://website-backend.computiq.tech/api/docs#/