mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-02 23:20:53 -04:00
Started react section
This commit is contained in:
@@ -16,6 +16,7 @@ export default {
|
|||||||
"/server/": require("../server/sidebar.json"),
|
"/server/": require("../server/sidebar.json"),
|
||||||
"/aws/": require("../aws/sidebar.json"),
|
"/aws/": require("../aws/sidebar.json"),
|
||||||
"/go": require("../go/sidebar.json"),
|
"/go": require("../go/sidebar.json"),
|
||||||
|
"/react": require("../react/sidebar.json"),
|
||||||
"/": [
|
"/": [
|
||||||
{
|
{
|
||||||
text: "Home",
|
text: "Home",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
# Frameworks
|
# Frameworks
|
||||||
|
|
||||||
[Nuxtjs](/nuxt/)
|
[Nuxtjs](/nuxt/)
|
||||||
|
|
||||||
|
[React](/react/)
|
||||||
|
|||||||
99
docs/react/components.md
Normal file
99
docs/react/components.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Components
|
||||||
|
|
||||||
|
Components are the building blocks of a React application. They contained logic and templates that are reused throughout your applications. Components are essentially functions that return some JSX that can then be imported and used by parent components.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export default function Component() {
|
||||||
|
return <h1>Some component!</h1>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
Props provide a way to pass data from a parent component into a child component to somehow influence their behavior. This is a one way flow, meaning changes to these props only flow from the parent to the child, but not the reverse.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Parent
|
||||||
|
function Parent() {
|
||||||
|
return <Child color="red" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child
|
||||||
|
function Child(props) {
|
||||||
|
return <h1>{props.color}</h1>;
|
||||||
|
}
|
||||||
|
// or
|
||||||
|
function Child({ color }) {
|
||||||
|
return <h1>{color}</h1>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Props however can also be functions, which is how you would handle passing data from the child back to the parent.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Parent() {
|
||||||
|
const onSearch = (term) => {
|
||||||
|
console.log("Searching for", term);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Child onSubmit={onSearch} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
function Child({ onSubmit }) {
|
||||||
|
const [term, setTerm] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit(term);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input value={term} onChange={(e) => setTerm(e.target.value)} />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Children Prop
|
||||||
|
|
||||||
|
There is a special prop called `children` which works similarly to slots in Vue. When you use a component and pass in some text or elements within the opening and closing tags, that gets passed as a prop `children` which you can use within your component.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Child({ children }) {
|
||||||
|
return <h1>{children}</h1>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Parent() {
|
||||||
|
return <Child>This is my children!</Child>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Typescript
|
||||||
|
|
||||||
|
When using Typescript, you can define an interface for your props to improve intelisense in your IDE.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface MyProps {
|
||||||
|
color: string | null; // can be either string or null
|
||||||
|
shade?: string; // optional prop
|
||||||
|
}
|
||||||
|
|
||||||
|
function Child({ color, shade });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Feeding classNames to child components
|
||||||
|
|
||||||
|
To pass classNames to your child components, we can use a package called `classnames` to aggregate multiple classes.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
function Child({ className }) {
|
||||||
|
const classes = classNames("myclass1 myclass2", className);
|
||||||
|
|
||||||
|
return <div className={classes}>My component with custom classes!</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
78
docs/react/context.md
Normal file
78
docs/react/context.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Context
|
||||||
|
|
||||||
|
Context is a way of sharing state with many child components without having to pass them via props.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// context/foo.ts
|
||||||
|
import { createContext, useState } from "react";
|
||||||
|
|
||||||
|
const MyContext = createContext();
|
||||||
|
|
||||||
|
function Provider({ children }) {
|
||||||
|
const [value, setValue] = useState(0);
|
||||||
|
|
||||||
|
const incrementValue = () => {
|
||||||
|
setValue(value + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const valueToShare = {
|
||||||
|
value,
|
||||||
|
incrementValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyContext.Provider value={valueToShare}>{children}</MyContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Provider };
|
||||||
|
export default MyContext;
|
||||||
|
|
||||||
|
// In wrapping component...
|
||||||
|
import { Provider } from "context/foo";
|
||||||
|
|
||||||
|
function Parent() {
|
||||||
|
return (
|
||||||
|
<Provider>
|
||||||
|
<Children />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In consuming component
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { MyContext } from "../context/foo";
|
||||||
|
|
||||||
|
function Child() {
|
||||||
|
const { value, incrementValue } = useContext(MyContext);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## useCallback
|
||||||
|
|
||||||
|
This hook is used to fix some bugs when calling functions stored within contexts from useEffect in a child component. Since useEffect requires the function to be added as a dependency in the second argument, it will rerun that effect whenever that function is called because calling it forces the context to rerender, which in turn creates a new version of that function. So to fix this, we need to use useCallback to create a stable reference to that function.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// context/foo.ts
|
||||||
|
import { createContext, useState, useCallback } from "react";
|
||||||
|
|
||||||
|
const MyContext = createContext();
|
||||||
|
|
||||||
|
function Provider({ children }) {
|
||||||
|
const [value, setValue] = useState(0);
|
||||||
|
|
||||||
|
const fetchValue = useCallback(() => {
|
||||||
|
newValue = fetch("localhost:8080");
|
||||||
|
setValue(newValue);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const valueToShare = {
|
||||||
|
value,
|
||||||
|
fetchValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyContext.Provider value={valueToShare}>{children}</MyContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
38
docs/react/index.md
Normal file
38
docs/react/index.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# React
|
||||||
|
|
||||||
|
React is a Javascript/Typescript framework for creating responsive single page applications in the web.
|
||||||
|
|
||||||
|
## Creating an App
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm create vite@latest <app-name> -- --template=react-ts
|
||||||
|
cd <app-name>
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Tailwindcss (Optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D tailwindcss postcss autoprefixer
|
||||||
|
npx tailwindcss init -p
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// tailwind.config.js
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* index.css */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
```
|
||||||
86
docs/react/jsx.md
Normal file
86
docs/react/jsx.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# JSX
|
||||||
|
|
||||||
|
## Rendering a List
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Fragment } from "react";
|
||||||
|
|
||||||
|
function Comp() {
|
||||||
|
const items = [
|
||||||
|
{ id: 1, name: "Item 1" },
|
||||||
|
{ id: 2, name: "Item 2" },
|
||||||
|
{ id: 3, name: "Item 3" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{items.map((item) => {
|
||||||
|
return <Fragment key={item.id}>{item.name}</Fragment>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional Rendering
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Comp() {
|
||||||
|
const items = [
|
||||||
|
{ id: 1, name: "Item 1", show: true },
|
||||||
|
{ id: 2, name: "Item 2", show: true },
|
||||||
|
{ id: 3, name: "Item 3", show: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{items.map((item) => {
|
||||||
|
return {item.show && <Fragment key={item.id}>{item.name}</Fragment>};
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checking if item is valid
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Comp() {
|
||||||
|
const items: object[] | null = null;
|
||||||
|
|
||||||
|
return <div>{items?.length || "No items"}</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Two-way data binding
|
||||||
|
|
||||||
|
In order to have two-way data binding between the component and an input or another child component, you need to make use of the `onChange` event or some other event callback.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
function Comp() {
|
||||||
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
|
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Handling number inputs
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
function Comp() {
|
||||||
|
const [value, setValue] = useState(0);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
// will be NaN if invalid input or empty
|
||||||
|
newValue = parseInt(e.target.value) || 0;
|
||||||
|
// do something with new value
|
||||||
|
};
|
||||||
|
|
||||||
|
// need || "" or else it will always have an annoying 0 in the input
|
||||||
|
return <input type="number" value={value || ""} onChange={handleChange} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
134
docs/react/redux.md
Normal file
134
docs/react/redux.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Redux
|
||||||
|
|
||||||
|
Redux is a centralized state store that can be accessed from any component in your application. It acts as basically a centralized reducer, where it has a state and an dispatch method to update that state. Redux Toolkit is a library that simplifies creating actions to interact with the store, and is the recommended approach going forward.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// store/slices/song.ts
|
||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { reset } from "../actions";
|
||||||
|
|
||||||
|
const songsSlice = createSlice({
|
||||||
|
name: "song",
|
||||||
|
initialState: [],
|
||||||
|
reducers: {
|
||||||
|
addSong(state, action) {
|
||||||
|
state.push(action.payload);
|
||||||
|
},
|
||||||
|
removeSong(state, action) {
|
||||||
|
// action.payload === string, the song we want to remove
|
||||||
|
const index = state.indexOf(action.payload);
|
||||||
|
state.splice(index, 1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// listen to manual actions not created by this generator
|
||||||
|
extraReducers(builder) {
|
||||||
|
builder.addCase(reset, (state, action) => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { addSong, removeSong } = songsSlice.actions;
|
||||||
|
// can also export this as the default
|
||||||
|
export const songsReducer = songsSlice.reducer;
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// store/actions.ts
|
||||||
|
// Declare common actions shared by multiple reducers
|
||||||
|
import { createAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
export const reset = createAction("app/reset");
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// store/index.ts
|
||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
import { songsReducer, addSong, removeSong } from "./slices/songsSlice";
|
||||||
|
import { reset } from "./actions";
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
songs: songsReducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export store and slice actions
|
||||||
|
export { store, reset, addSong, removeSong };
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// index.ts
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import App from "./App";
|
||||||
|
import { store } from "./store";
|
||||||
|
|
||||||
|
const rootElement = document.getElementById("root");
|
||||||
|
const root = createRoot(rootElement);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// components/songs.tsx
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { createRandomSong } from "../data";
|
||||||
|
import { addSong, removeSong } from "../store";
|
||||||
|
|
||||||
|
function SongPlaylist() {
|
||||||
|
// object to call actions
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
// fetch state
|
||||||
|
// can also filter/map state within here if you want
|
||||||
|
const songPlaylist = useSelector((state) => {
|
||||||
|
return state.songs;
|
||||||
|
});
|
||||||
|
|
||||||
|
// dispatch actions with some payload
|
||||||
|
const handleSongAdd = (song) => {
|
||||||
|
dispatch(addSong(song));
|
||||||
|
};
|
||||||
|
const handleSongRemove = (song) => {
|
||||||
|
dispatch(removeSong(song));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderedSongs = songPlaylist.map((song) => {
|
||||||
|
return (
|
||||||
|
<li key={song}>
|
||||||
|
{song}
|
||||||
|
<button
|
||||||
|
onClick={() => handleSongRemove(song)}
|
||||||
|
className="button is-danger"
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="content">
|
||||||
|
<div className="table-header">
|
||||||
|
<h3 className="subtitle is-3">Song Playlist</h3>
|
||||||
|
<div className="buttons">
|
||||||
|
<button
|
||||||
|
onClick={() => handleSongAdd(createRandomSong())}
|
||||||
|
className="button is-link"
|
||||||
|
>
|
||||||
|
+ Add Song to Playlist
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul>{renderedSongs}</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SongPlaylist;
|
||||||
|
```
|
||||||
241
docs/react/routing.md
Normal file
241
docs/react/routing.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# Routing
|
||||||
|
|
||||||
|
## Vanilla Routing with Context
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// context/navigation.ts
|
||||||
|
import { createContext, useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const NavigationContext = createContext();
|
||||||
|
|
||||||
|
function NavigationProvider({ children }) {
|
||||||
|
const [currentPath, setCurrentPath] = useState(window.location.pathname);
|
||||||
|
|
||||||
|
// handle front and back arrows
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = () => {
|
||||||
|
setCurrentPath(window.location.pathname);
|
||||||
|
};
|
||||||
|
window.addEventListener("popstate", handler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("popstate", handler);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const navigate = (to) => {
|
||||||
|
window.history.pushState({}, "", to);
|
||||||
|
setCurrentPath(to);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavigationContext.Provider value={{ currentPath, navigate }}>
|
||||||
|
{children}
|
||||||
|
</NavigationContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { NavigationProvider };
|
||||||
|
export default NavigationContext;
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Route component for conditionally rendering components
|
||||||
|
import useNavigation from "../hooks/use-navigation";
|
||||||
|
|
||||||
|
function Route({ path, children }) {
|
||||||
|
const { currentPath } = useNavigation();
|
||||||
|
|
||||||
|
if (path === currentPath) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Route;
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// example link component
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useNavigation from "../hooks/use-navigation";
|
||||||
|
|
||||||
|
function Link({ to, children, className, activeClassName }) {
|
||||||
|
const { navigate, currentPath } = useNavigation();
|
||||||
|
|
||||||
|
const classes = classNames(
|
||||||
|
"text-blue-500",
|
||||||
|
className,
|
||||||
|
currentPath === to && activeClassName // apply styling if currently on this page
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClick = (event) => {
|
||||||
|
// handle opening new tab, use default behavior
|
||||||
|
if (event.metaKey || event.ctrlKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
navigate(to);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a className={classes} href={to} onClick={handleClick}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Link;
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Navbar component
|
||||||
|
import Link from "./Link";
|
||||||
|
|
||||||
|
function NavBar() {
|
||||||
|
const links = [
|
||||||
|
{ label: "Dropdown", path: "/" },
|
||||||
|
{ label: "Accordion", path: "/accordion" },
|
||||||
|
{ label: "Buttons", path: "/buttons" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderedLinks = links.map((link) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={link.label}
|
||||||
|
to={link.path}
|
||||||
|
className="mb-3"
|
||||||
|
activeClassName="font-bold border-l-4 border-blue-500 pl-2"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sticky top-0 overflow-y-scroll flex flex-col items-start">
|
||||||
|
{renderedLinks}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavBar;
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// App.ts
|
||||||
|
import Sidebar from "./components/Sidebar";
|
||||||
|
import Route from "./components/Route";
|
||||||
|
import AccordionPage from "./pages/AccordionPage";
|
||||||
|
import DropdownPage from "./pages/DropdownPage";
|
||||||
|
import ButtonPage from "./pages/ButtonPage";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto grid grid-cols-6 gap-4 mt-4">
|
||||||
|
<Sidebar />
|
||||||
|
<div className="col-span-5">
|
||||||
|
<Route path="/accordion">
|
||||||
|
<AccordionPage />
|
||||||
|
</Route>
|
||||||
|
<Route path="/">
|
||||||
|
<DropdownPage />
|
||||||
|
</Route>
|
||||||
|
<Route path="/buttons">
|
||||||
|
<ButtonPage />
|
||||||
|
</Route>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using React Router library
|
||||||
|
|
||||||
|
Documentation [here](https://reactrouter.com/home).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// main.tsx
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
|
|
||||||
|
import HomePage from "./pages/HomePage";
|
||||||
|
import NotFoundPage from "./pages/NotFoundPage";
|
||||||
|
import ProfilePage from "./pages/ProfilePage";
|
||||||
|
import ProfilesPage from "./pages/ProfilesPage";
|
||||||
|
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <HomePage />,
|
||||||
|
errorElement: <NotFoundPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/profiles",
|
||||||
|
element: <ProfilesPage />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/profiles/:profileId",
|
||||||
|
element: <ProfilePage />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ProfilesPage.tsx with child component
|
||||||
|
import { NavLink, Outlet } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function ProfilesPage() {
|
||||||
|
const profiles = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{profiles.map((profile) => (
|
||||||
|
// NavLink allows you to apply styling if the link is active
|
||||||
|
<NavLink
|
||||||
|
key={profile}
|
||||||
|
to={`/profiles/${profile}`}
|
||||||
|
className={({ isActive }) => {
|
||||||
|
return isActive ? "text-primary-700" : "";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Profile {profile}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<!-- Outlet displays child component links if active -->
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ProfilePage component using query params
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function ProfilePage() {
|
||||||
|
const params = useParams<{ profileId: string }>();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Profile Page {params.profileId}</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
14
docs/react/sidebar.json
Normal file
14
docs/react/sidebar.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "React Basics",
|
||||||
|
"items": [
|
||||||
|
{ "text": "Introduction", "link": "/react/" },
|
||||||
|
{ "text": "JSX", "link": "/react/jsx" },
|
||||||
|
{ "text": "Components", "link": "/react/components" },
|
||||||
|
{ "text": "State", "link": "/react/state" },
|
||||||
|
{ "text": "Context", "link": "/react/context" },
|
||||||
|
{ "text": "Routing", "link": "/react/routing" },
|
||||||
|
{ "text": "Redux", "link": "/react/redux" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
175
docs/react/state.md
Normal file
175
docs/react/state.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# State and Events
|
||||||
|
|
||||||
|
State allows you to have the user interace with your application, update some data under the hood, and have that reflected in the page.
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
Events means how you handle some user actions in your application. A full list of possible events can be found [here](https://react.dev/reference/react-dom/components/common#). Essentially, you define functions that are called when certain actions are performed on elements in your page.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function App() {
|
||||||
|
const handleClick = () => {
|
||||||
|
console.log("button clicked");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={handleClick}>Click me</button>;
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
console.log("Inline function click");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Click me too!
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## useState
|
||||||
|
|
||||||
|
State tells your application to rerender the page when something changes in your application.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
setCount(count + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button onClick={handleClick} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## useReducer
|
||||||
|
|
||||||
|
A Reducer is a way of combining multiple state variables into one object. This allows you to reduce the number of rerenders if multiple pieces of state are changing at the same time. `useReducer` takes a `reducer` function and an initial state. This `reducer` function will run whenever we call `dispatch`, and the argument to `dispatch` will be passed as an argument `action` along with the current state. The `action` argument is usually an object which tells the reducer what type of update to do. Whatever is returned from this function will be the new state. The `reducer` function cannot include async/await, promises, requests, etc. It is only for updating the state. You should not update the state directly unless you are using Immer, which is a library that automatically returns the new state for you.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import produce from "immer";
|
||||||
|
import { useReducer } from "react";
|
||||||
|
|
||||||
|
// Use constants for action to prevent typos
|
||||||
|
const INCREMENT_COUNT = "increment";
|
||||||
|
const SET_VALUE_TO_ADD = "change_value_to_add";
|
||||||
|
const DECREMENT_COUNT = "decrement";
|
||||||
|
const ADD_VALUE_TO_COUNT = "add_value_to_count";
|
||||||
|
|
||||||
|
const reducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case INCREMENT_COUNT:
|
||||||
|
state.count = state.count + 1;
|
||||||
|
return;
|
||||||
|
// If not using immer, you would need to do something like...
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
count: state.count + 1,
|
||||||
|
};
|
||||||
|
case DECREMENT_COUNT:
|
||||||
|
state.count = state.count - 1;
|
||||||
|
return;
|
||||||
|
case ADD_VALUE_TO_COUNT:
|
||||||
|
state.count = state.count + state.valueToAdd;
|
||||||
|
state.valueToAdd = 0;
|
||||||
|
return;
|
||||||
|
case SET_VALUE_TO_ADD:
|
||||||
|
state.valueToAdd = action.payload;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function CounterPage({ initialCount }) {
|
||||||
|
// wrapper reducer with produce to use immer
|
||||||
|
const [state, dispatch] = useReducer(produce(reducer), {
|
||||||
|
count: initialCount,
|
||||||
|
valueToAdd: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const increment = () => {
|
||||||
|
dispatch({
|
||||||
|
type: INCREMENT_COUNT,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const decrement = () => {
|
||||||
|
dispatch({
|
||||||
|
type: DECREMENT_COUNT,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleChange = (event) => {
|
||||||
|
const value = parseInt(event.target.value) || 0;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SET_VALUE_TO_ADD,
|
||||||
|
payload: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ADD_VALUE_TO_COUNT,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CounterPage;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Binding inputs
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Input({ onSubmit }) {
|
||||||
|
const [text, setText] = useState("");
|
||||||
|
|
||||||
|
const handleChange = (event) => {
|
||||||
|
setText(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onSubmit(text);
|
||||||
|
setText("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input className="input" value={title} onChange={handleChange} />
|
||||||
|
<button className="button">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## useEffect
|
||||||
|
|
||||||
|
useEffect is a function that runs when the application is initially rendered and sometimes when it is rerendered. The arrow function provided is always called on the initial render, and if a variable passed in the second argument is changed in the rerender, then the arrow function is called again. If no second argument is passed, then the function is called on every rerender.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
function Comp() {
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Run on initial render");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Run on every render");
|
||||||
|
});
|
||||||
|
|
||||||
|
const [text, setText] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Run when 'text' is changed");
|
||||||
|
}, [text]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The only thing we can return from useEffect is a function, and this function gets called before the next time the useEffect function is run, so from the second render on.
|
||||||
Reference in New Issue
Block a user