curriculum/challenges/english/blocks/review-react-forms-data-fetching-and-routing/67c9e932c6c4234532d46394.md
onChange events. This gives you complete control over the form data and allows instant validation and conditional rendering.import { useState } from "react";
function App() {
const [name, setName] = useState("");
const handleChange = (e) => {
setName(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(name);
};
return (
<>
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your name</label>
<input value={name} id="name" onChange={handleChange} type="text" />
<button type="submit">Submit</button>
</form>
</>
);
}
export default App;
useState hook, uncontrolled inputs in HTML maintain their own internal state with the help of the DOM. Since the DOM controls the input values, you need to pull in the values of the input fields with a ref.import { useRef } from "react";
function App() {
const nameRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log(nameRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your</label>{" "}
<input type="text" ref={nameRef} id="name" />
<button type="submit">Submit</button>
</form>
);
}
export default App;
useActionState Hook"use server";
async function submitForm(formData) {
const name = formData.get("name");
return { message: `Hello, ${name}!` };
}
The "user server" directive marks the function as a server action.
useActionState Hook: This hook updates state based on the outcome of a form submission. Here's the basic syntax of the useActionState hook:const [state, action, isPending] = useActionState(actionFunction, initialState, permalink);
state is the current state the action returns.action is the function that triggers the server action.isPending is a boolean that indicates whether the action is currently running or not.actionFunction parameter is the server action itself.initialState is the parameter that represents the starting point for the state before the action runs.permalink is an optional string that contains the unique page URL the form modifies.const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Since data fetching is a side effect, it's best to use the Fetch API inside of a useEffect hook.
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!res.ok) {
throw new Error("Network response was not ok");
}
const data = await res.json();
setData(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
Then you can render a loading message if the data fetching is not complete, an error message if there was an error fetching the data, or the results.
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>{error.message}</p>;
}
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
If you want to use Axios, you need to install and import it:
npm i axios
import axios from "axios";
Then you can fetch the data using axios.get:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
setData(res.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
To fetch data using the useSWR hook, you need to first install and import it.
npm i swr
import useSWR from "swr";
Here is how you can use the hook to fetch data:
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((res) => res.json());
const FetchTodos = () => {
const { data, error } = useSWR(
"https://jsonplaceholder.typicode.com/todos",
fetcher
);
if (!data) {
return <h2>Loading...</h2>;
}
if (error) {
return <h2>Error: {error.message}</h2>;
}
return (
<>
<h2>Todos</h2>
<div>
{data.map((todo) => (
<h3 key={todo.id}>{todo.title}</h3>
))}
</div>
</>
);
};
export default FetchTodos;
useOptimistic HookuseOptimistic Hook: This hook is used to keep UIs responsive while waiting for an async action to complete in the background. It helps manage "optimistic updates" in the UI, a strategy in which you provide immediate updates to the UI based on the expected outcome of an action, like waiting for a server response.Here is the basic syntax:
const [optimisticState, addOptimistic] = useOptimistic(actualState, updateFunction);
optimisticState is the temporary state that updates right away for a better user experience.addOptimistic is the function that applies the optimistic update before the actual state changes.actualState is the real state value that comes from the result of an action, like fetching data from a server.updateFunction is the function that determines how the optimistic state should update when called.Here is an example of using the useOptimistic hook in a TaskList component:
"use client";
import { useOptimistic } from "react";
export default function TaskList({ tasks, addTask }) {
const [optimisticTasks, addOptimisticTask] = useOptimistic(
tasks,
(state, newTask) => [...state, { text: newTask, pending: true }]
);
async function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
addOptimisticTask(formData.get("task"));
addTask(formData);
e.target.reset();
}
return <></>;
}
startTransition: This is used to render part of the UI and mark a state update as a non-urgent transition. This allows the UI to be responsive during expensive updates. Here is the basic syntax:startTransition(action);
The action performs a state update or triggers some transition-related logic. This ensures that urgent UI updates (like typing or clicking) are not blocked.
useMemo HookuseMemo Hook: This hook is used to memoize computed values. Here is an example of memoizing the result of sorting a large array. The expensiveSortFunction will only run when largeArray changes:const memoizedSortedArray = useMemo(
() => expensiveSortFunction(largeArray),
[largeArray]
);
useCallback HookuseCallback Hook: This is used to memoize function references.const handleClick = useCallback(() => {
// code goes here
}, [dependency]);
React.memo: This is used to memoize a component to prevent it from unnecessary re-renders when its prop has not changed.const MemoizedComponent = React.memo(({ prop }) => {
return (
<>
</>
)
});
react and react-dom packages:"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
package.json File: This is a key configuration file in projects that contains metadata about your project, including its name, version, and dependencies. It also defines scripts, licensing information, and other settings that help manage the project and its dependencies.package-lock.json File: This file will lock down the exact versions of all packages that your project is using. When you update a package, then the new versions will be updated in the lock file as well.node_modules Folder: This folder contains the actual code for the dependencies listed in your package.json file, including both your project's direct dependencies and any dependencies of those dependencies."devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"vite": "^6.0.5"
}
npm i react-router
Then inside of the main.jsx or index.jsx file, you will need to setup the route structure like this:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
</Routes>
</BrowserRouter>
</StrictMode>
);
The path and element are used to couple the URL and UI components together. In this case, we are setting up a route for the homepage that points to the App component.
<Routes>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="products">
<Route index element={<ProductsHome />} />
<Route path=":category" element={<Category />} />
<Route path=":category/:productId" element={<ProductDetail />} />
<Route path="trending" element={<Trending />} />
</Route>
</Routes>
The index prop in these examples is meant to represent the default route for a given path segment. So the Home component will be shown at the root / path while the ProductsHome component will be shown at the /products path.
<Route path="products">
<Route path="trending" element={<Trending />} />
</Route>
In the example above, the path for the trending products will be products/trending.
<Route path=":category" element={<Category />} />
In this example we have a dynamic segment called category. When a user navigates to a URL like products/brass-instruments, then the view will change to the Category component and you can dynamically fetch the appropriate data based on the segment.
useParams Hook: This hook is used to access the dynamic parameters from a URL path.import { useParams } from "react-router";
export default function Category() {
let params = useParams();
}
Here is an example of creating a custom request handler:
export async function GET() {
const res = await fetch("https://example-api.com");
const data = await res.json();
return Response.json({ data });
}
Image component extends the native HTML img element and allows for faster page loads and size optimizations. This means that images will only load when they enter the viewport and the Image component will automatically serve correctly sized images for each device.import Image from "next/image";
export default function Page() {
return (
<Image src="link-to-image-goes-here" alt="descriptive-title-goes-here" />
);
}
createContext is used to create a context object which represents the context that other components will read. The Provider is used to supply context values to the child components.import { useState, createContext } from "react";
const CounterContext = createContext();
const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
};
export { CounterContext, CounterProvider };
Redux: Redux handles state management by providing a central store and strict control over state updates. It uses a predictable pattern with actions, reducers, and middleware. Actions are payloads of information that send data from your application to the Redux store, often triggered by user interactions. Reducers are functions that specify how the state should change in response to those actions, ensuring the state is updated in an immutable way. Middleware, on the other hand, acts as a bridge between the action dispatching and the reducer, allowing you to extend Redux's functionality (e.g., logging, handling async operations) without modifying the core flow.
Zustand: This state management solution is ideal for small to medium-scale applications. It works by using a useStore hook to access state directly in components and pages. This lets you modify and access data without needing actions, reducers, or a provider.
react-devtools npm package. After installing React DevTools and opening a React app in the browser, open the browser developer tools to access the two extra tabs provided for debugging React – Components and Profiler.Review React routing, state management, forms and data fetching.