diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js
index 65f6333..930ca57 100644
--- a/docs/.vitepress/config.js
+++ b/docs/.vitepress/config.js
@@ -16,6 +16,7 @@ export default {
"/server/": require("../server/sidebar.json"),
"/aws/": require("../aws/sidebar.json"),
"/go": require("../go/sidebar.json"),
+ "/react": require("../react/sidebar.json"),
"/": [
{
text: "Home",
diff --git a/docs/frameworks.md b/docs/frameworks.md
index 5f8bc08..fc88d73 100644
--- a/docs/frameworks.md
+++ b/docs/frameworks.md
@@ -1,3 +1,5 @@
# Frameworks
[Nuxtjs](/nuxt/)
+
+[React](/react/)
diff --git a/docs/react/components.md b/docs/react/components.md
new file mode 100644
index 0000000..9cea729
--- /dev/null
+++ b/docs/react/components.md
@@ -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
Some component!
;
+}
+```
+
+## 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
+function Child(props) {
+ return
{props.color}
;
+}
+// or
+function Child({ color }) {
+ return
{color}
;
+}
+```
+
+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 ;
+}
+
+import { useState } from "react";
+
+function Child({ onSubmit }) {
+ const [term, setTerm] = useState("");
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ onSubmit(term);
+ };
+
+ return (
+
+ );
+}
+```
+
+### 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
{children}
;
+}
+
+function Parent() {
+ return This is my children!;
+}
+```
+
+### 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
My component with custom classes!
;
+}
+```
diff --git a/docs/react/context.md b/docs/react/context.md
new file mode 100644
index 0000000..1301679
--- /dev/null
+++ b/docs/react/context.md
@@ -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 (
+ {children}
+ );
+}
+
+export { Provider };
+export default MyContext;
+
+// In wrapping component...
+import { Provider } from "context/foo";
+
+function Parent() {
+ return (
+
+
+
+ );
+}
+
+// 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 (
+ {children}
+ );
+}
+```
diff --git a/docs/react/index.md b/docs/react/index.md
new file mode 100644
index 0000000..ef81e42
--- /dev/null
+++ b/docs/react/index.md
@@ -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 -- --template=react-ts
+cd
+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;
+```
diff --git a/docs/react/jsx.md b/docs/react/jsx.md
new file mode 100644
index 0000000..e39be33
--- /dev/null
+++ b/docs/react/jsx.md
@@ -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 (
+
;
+}
+```
+
+## 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 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 ;
+}
+```
diff --git a/docs/react/redux.md b/docs/react/redux.md
new file mode 100644
index 0000000..833ed25
--- /dev/null
+++ b/docs/react/redux.md
@@ -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(
+
+
+
+);
+```
+
+```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 (
+
+ );
+}
+```
diff --git a/docs/react/sidebar.json b/docs/react/sidebar.json
new file mode 100644
index 0000000..df2bf08
--- /dev/null
+++ b/docs/react/sidebar.json
@@ -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" }
+ ]
+ }
+]
diff --git a/docs/react/state.md b/docs/react/state.md
new file mode 100644
index 0000000..e1411da
--- /dev/null
+++ b/docs/react/state.md
@@ -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 (
+
+ ;
+
+
+ );
+}
+```
+
+## 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 ;
+}
+```
+
+## 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 (
+
+
+
+ );
+}
+```
+
+## 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.