React
React
Tech Stack
Vite: Server + bundler; updates apps without refresh, thanks to Hot Module Replacement (HMR).
Next JS: Custom Router + Bundler + server side/client side rendering (configure options like TypeScript, ESLint, and Tailwind CSS)
Create React App: Boilerplate react app
Cargo-React: A Boilerplate for creating Component Libraries in React + Typescript + StoryBook + Styled Components
Flow: Type Checker
styled-components: Create react components with css (can pass prop too). Then can wrap in component.
React Forms Hook: Use
{register, handleSubmit, reset} = useForms()NodeJS: Run the server
Express: Backend Server (
app.get()andapp.listen())nodemon: Automatically restarts your server when file changes are detected
MongoDB: Non-relational DB, open-source (
mongodto connect;mongocli to interact)- How to connect?
MongoClient->await client.connect()->db = client.db('name')
- How to connect?
Axios: Automatically allow requests to the back-end when users interact with the UI
Semantic UI: UI library
Nodemailer: Email client to send email; nodeJS based
Firebase Auth
- Firebase FrontEnd:
signInWithEmailAndPassword(getAuth(), email, pass)SingOut(getAuth())and- Send to new page using props.history
onAuthStateChanged(getAuth(), user => ())in auseEffect(in parent component)- Can use token from
await user.getIdToken() - Firebase Backend:
firebase-adminadmin- Make a express middleware using
app.use()and check authToken
Firebase: Using
firebase-toolsData Storage- Doc based, no rows (so doc collection, not tables)
- Set rules to allow read/write
.firebasercProject details;firebase.jsonrules and indexes;firestore.rulesData rules- Set:
docRef = firstore.doc(/users/{user.id})docRef.set() - Retrieve:
firestore.collection(users).doc(user.id). Use.get()or.onSnapshot()for realtime
File Storagestorge.rulesuseReffor get the file upload detailsfileRef = storage.ref().child(filepath)fileRef.put()- Use uploadTask.on(STAGE_CHANGED, next, err, complete)
ref = uploadTask.snapshot.ref - link:
ref.getDownloadUrl()
Claim Based Auth- Firebase auth is based on JWT
- Create role-based access
admin = require(firebase-admin)->admin.initializeApp(credentials, databaseURL)- Use cred of service account you want to give access
admin.auth().setCustomUserClaims()- Then in storage rules update access for
request.auth.token.admin == True
Serverless Funfirebase init functionscreates a functions folder containing your Cloud Functions code, a package.json file for dependencies, and an index.js file as the main source file.firebase functions:set:config a={} ..to set config- Get then using
'firebase-functions'.config().a - Trigger:
functions.auth.user().onCreate({} => {}) - Update cloud:
firebase deploy --only functions
Hosting- Update
firebase.jsonwith hosting info (public path etc.) - Do
firebase deploy --only hosting
- Update
JWT Auth
- 3F
Knowledge,Ownership,Biological - JSON Web Tokens (JWTs)
jwt.sign - a header (type and signing algorithm), a payload (user information), and a signature (proves authenticity)
- Can be stored in
localStoragein frontend
- 3F
Email Verify
- Sendgrid to send email, use uuid for each request (saved in db entry of that user).
OAuth 2
- generating a special URL, redirecting the user to the service provider’s site, obtaining a code, and using that code to access user information
- Need to set
Allowed Callback URLs,Allowed Origin,Allowed Logout - OAuth client will generate link -> useEffect to get in login page
- Callback link will get info (may need to call service again)
- Call to login with token in url -> useProp to get token and redirect to home if present
AuthO
- Wrapper for Root App
<Auth0Provider clientId domain redirect_url onRedirectCallback> {isAuth, loginWithRedirect, logout} = useAuth0hook to use in login component
- Wrapper for Root App
Amazon Cognito
- handles tasks like storing passwords, logging in, signing up, email verification, and password resetting
- user pools to manage user accounts and identity pools to manage permissions
- Cognito sends a six-digit code for verification and password resets, which the user enters in the application
React for Accessibility:
- Semantic HTML: Tags with meaning (not div, span)
- React Semantic tags:
React.Fragmentinsted of div. - Focus on Components after update: Create and attach ref, focus on
componentDidUpdate - Skip navigation tab by a
skip navbutton to id of important components - Use
onFoucsandonBlurto show hide/options - For testing, use
keyboardoraccessibility audits in Chrome DevTools by selecting LighthouseorscreenReaderslike VoiceOver and NVDA
SSR in React
- Build using
next build - Redux can help with performance
- On server, use
nextas app and set express route toapp.render(req, res, '/link') - Does code splitting automatically (each component bundled)
- Next.js supports advanced features like dynamic imports and dynamic React components, which enable on-demand loading of specific features to optimize load times.
- Webpack and Browserify are other options for splitting
- Build using
CSS in React
Import styles from app.module.cssand access asstyles.<className>- CSS Modules are 1-1 mapping of css with each component
- Can use
composeto inherit properties of other class classnamescxto combine className css for React Componentshttps://animate.style/to find animations
Security
- Flow: Type Checker
- Always use SSL Certificate and HTTPS
- Salting (append deterministic random string for each user) + peppering (add common string)
- Always use backend for data processing (front-end no data then it needs to know)
- Tell user errors but without too much info
- Use
React.StrictModeto get warnings on Unsafe Lifecycle Methods, Unsafe Lifecycle Methods, Unexpected Side Effects
GraphQL in React:
- schema defines your structure of data in your queries.
- UseEffect to create query and fetch it (will always be post)
- Can take input in query to make it dynamic
- Can search using
{a{b(first:10)}}
React
- React compiler can make
React Components:
- Props can be used to send data as
(props)or deconstruct it as({val1, val2}) - Keys can be used to keep item in sync
- Props can be used to send data as
Hooks:
| Hook | Purpose | Syntax |
|---|---|---|
useState | Local state | [val, setVal] = useState(init) |
useEffect | Run side effects after render (data fetch, subscriptions, timers) | useEffect(fn, [deps]) |
useReduce | Fun on oldState, newState | [val, setVal] = useReduce((old, new) => (), default) |
createContext | Share global context with child components | const C = createContext(default) |
useContext | Consume context value | const v = useContext(C) |
useRef | Access DOM components or store mutable value (add as ) | const r = useRef(init); r.current |
useMemo | Memoize expensive calculation once and not on re-render | useMemo(fn, [deps]) |
useCallback | Memoize callback when need to pass to child components / not allow rerender | useCallback(fn, [deps]) |
React.memo | Memoize entire component (child component) re-render | export default React.memo(Comp) |
- Custom Hooks is just a fun with react hooks inside
useHistoryreplaced byuseNavigationin new version for browser tab historyuseNavigateto just navigate to another page like link (can send state too which can be accessed byuseLocation)
Routes:
react-router-domor <RouterProvider router={path, element}>/url/:paramcan be accessed byuseParams- Can load data beforehand using
<RouterProvider loader= () => {}>or useuseLoaderData(). Can use param too. BrowserRouterused HTML5 history API to manage navigation. Keeps UI in sync with route.- Use
<Route path='*'>for 404 pages <NavLink>automatically adds anactiveclass to the link when its path matches the current URL, which can be styled using CSSuseNavigateto just navigate to another page like link (can send state too which can be accessed byuseLocation)- Nested Routing
<Outlet>in parent component to tell where to render child component- Use
<Route index element={}>if you need default route for outlet
JSX:
- Allow HTML
- Add data/use JS using
{}
Testing:
- Separate as much logic
- Check input/output in redux
- Reducers: Separate Slice Definition and check each functions
- Selectors: Create fake state and pass through selectors
- Jest
- Use
testoritto define test - Test with data:
expect().toBe/toBeGreater.. expect(received).toEqual(expect.arrayContaining(subset))npm run test -- -- coveragecan show test coverage- Use
react-test-rendererto get snapshot of renderer.cmt = renderer.create(Component)->cmt.toJSON()(to get snapshot);renderer.act(to act on renderer. - Can use
describe()fun as a super class for tests.
- Use
- React Dev Tool: Component Inspection(State, Props), Console Commands ($r), Profiler (perf/timings)
- Lighthouse Report
Redux
Alternative:
- Recoil: Simpler, wrap your app in
RecoilRoot. usesatomsto create pieces of state - MobX: OOPs based, Use the
observerfunction from mobx-react-lite to ensure components re-render when MobX state changes. Create a context withcreateContextto provide MobX state to components.
A Redux app has a single
storethat is passed to React components via a<Provider>componentRedux state is updated by “reducer functions”:
- Reducers always calculate a new state immutably, by copying existing state values and modifying the copies with the new data
- The Redux Toolkit
createSlicefunction generates “slice reducer” functions for you, and lets you write “mutating” code that is turned into safe immutable updates - Those slice reducer functions are added to the
reducerfield inconfigureStore, and that defines the data and state field names inside the Redux store
React components read data from the store with the
useSelectorhook- Selector functions receive the whole
stateobject, and should return a value - Selectors will re-run whenever the Redux store is updated, and if the data they return has changed, the component will re-render
- Selector functions receive the whole
React components dispatch actions to update the store using the
useDispatchhookcreateSlicewill generate action creator functions for each reducer we add to a slice- Call
dispatch(someActionCreator())in a component to dispatch an action - Reducers will run, check to see if this action is relevant, and return new state if appropriate
- Temporary data like form input values should be kept as React component state or plain HTML input fields. Dispatch a Redux action to update the store when the user is done with the form.
If you’re using TypeScript, the initial app setup should define TS types for
RootStateandAppDispatchbased on the store, and export pre-typed versions of the React-ReduxuseSelectoranduseDispatchhooksWe can create a Redux store using the Redux Toolkit
configureStoreAPIconfigureStoreaccepts areducerfunction as a named argumentconfigureStoreautomatically sets up the store with good default settings
Redux logic is typically organized into files called “slices”
- A “slice” contains the reducer logic and actions related to a specific feature / section of the Redux state
- Redux Toolkit’s
createSliceAPI generates action creators and action types for each individual reducer function you provide
Redux reducers must follow specific rules
- Should only calculate a new state value based on the
stateandactionarguments - Must make immutable updates by copying the existing state
- Cannot contain any asynchronous logic or other “side effects”
- Redux Toolkit’s
createSliceAPI uses Immer to allow “mutating” immutable updates
- Should only calculate a new state value based on the
Reading values from the state is done with functions called “selectors”
- Selectors accept
(state: RootState)as their argument and either return a value from the state, or derive a new value - Selectors can be written in slice files, or inline in the
useSelectorhook
- Selectors accept
Async logic is typically written in special functions called “thunks”
- Thunks receive
dispatchandgetStateas arguments - Redux Toolkit enables the
redux-thunkmiddleware by default
- Thunks receive
React-Redux allows React components to interact with a Redux store
- Wrapping the app with
<Provider store={store}>enables all components to use the store - The
useSelectorhook lets React components read values from the Redux store - The
useDispatchhook lets components dispatch actions - For TS usage, we create pre-typed
useAppSelectoranduseAppDispatchhooks - Global state should go in the Redux store, local state should stay in React components
- Wrapping the app with
Design Patterns
Common problems we can abstract
Layout Component (split screens, lists and items, and modals)
- split screens: Component with divs
- List: Individual list component, main component (can pass as
{...{ [resourceName]: item} }==resourceName = {item})
Container Component: Parent component that load data
- Create a patent component with the fetch hook.
- Eg: Send user
React.children.map(props, child => return React.CloneElement(child, {user} ))
Uncontrolled (Manage own internal state)/ Controlled (parent manages the state, easier to test/reuse)
- Uncontrolled: UseRef and get value when submitting
- Controlled: UseState to save; useEffect to validate
Higher-order components (HOCs): functions that take a component and return a new component like a wrapper
- Fun with component as input: return a function that takes in props, logs them and then return the component with props
Functional Programming
- Makes data less mutable
- Less external datapoints
- Recursive components: components that refer to themselves within their own body. (Add stopping condition when not object)
- Composition: the functional programming analog to inheritance in object-oriented programming.
- Wrap the child in a fun and return with more/updated props
- Partially applied components: Take in component and semi-props, return component with those semi-props
Load data in server before SSR
- When sending in express server, change data to add
<script>${window.prelodedArticles=value}</script>in the DOM. - can access in React component (has access to dom)
- OR use Context
- When sending in express server, change data to add
Function-Based Organization: separate directories for pages, network, utilities, hooks, and reducers.
Feature-Based Organization: features of the application, such as articles, sign-ups, and subscriptions
SSR Without NextJS
import express from 'express';
import React from 'react';
import { ServerStyleSheet } from 'styled-components';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import path from 'path';
import fs from 'fs';
import App from './src/App';
const app = express();
app.use(express.static('./build', { index: false }))
const articles = [
{ title: 'Article 1', author: 'Bob' },
{ title: 'Article 2', author: 'Betty' },
{ title: 'Article 3', author: 'Frank' },
];
app.get('/api/articles', (req, res) => {
const loadedArticles = articles;
res.json(loadedArticles);
});
app.get('/*', (req, res) => {
const sheet = new ServerStyleSheet();
const reactApp = renderToString(
sheet.collectStyles(
<StaticRouter location={req.url}>
<App />
</StaticRouter>
)
);
const templateFile = path.resolve('./build/index.html');
fs.readFile(templateFile, 'utf8', (err, data) => {
if (err) {
return res.status(500).send(err);
}
return res.send(
data.replace('<div id="root"></div>', `<div id="root">${reactApp}</div>`)
.replace('{{ styles }}', sheet.getStyleTags())
)
});
});
app.listen(8080, () => {
console.log('Server is listening on port 8080');
});