The term hook often implies a mechanism for extending or customizing behavior by attaching or associating code with specific events or points in the program's execution.
- React Hooks are functions that let us hook into the React state and lifecycle features from function components.
- Hooks promote code reuse and organization by allowing logic to be extracted and shared across components.
- Hooks are a more direct way to use the React features such as state, lifecycle, context, and refs. E.g.:
useState
for managing stateuseEffect
for handling side effects- A side effect refers to any external operation, such as data fetching or DOM manipulation, initiated by a component that occurs outside the usual component rendering process.
useContext
for sharing state between components
- Hooks can be used in components or other hooks.
- Example of
useState
,useEffect
anduseRef
hooks:
import { useState, useEffect, useRef } from 'react';
function Example() {
const [count, setCount] = useState(0);
const testElement = useRef(null);
useEffect(() => {
// Update div element's color based on count
testElement.current?.style.setProperty('color', `rgb(${count * 5}, 0, 0)`);
}, [count]); // Only re-run the effect if count changes
return (
<div ref={testElement}>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
- Hooks are JavaScript/TypeScript functions, but they impose additional rules:
- Hooks should be called at the top level of a React function component or a custom hook, not inside nested functions, conditions, or loops.
- Only call Hooks from React components or custom Hooks. Don't call Hooks from regular JavaScript functions.
- Hooks start with the word
use
so that React knows to treat them as Hooks.
useState
is a Hook that lets you add React state to function components.- state is a data structure that represents the parts of the app that can change.
- The name "React" comes from the fact that React components are reactive, i.e., they react to state changes. So when you want to update the UI, you simply update the state, and React will automatically render the UI.
useState
returns a pair of values: the current state and a function that updates it.- Example:
import {useState} from 'react';
function Example() {
const [name, setName] = useState('');
return (
<div>
<p>You entered: {name}</p>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
}
export default Example;
useEffect
is a Hook that lets you perform side effects in function components.- Side effects are operations that affect other components or the outside world and can include data fetching, setting up a subscription, and manually changing the DOM.
useEffect
runs after the component renders and after every re-render.useEffect
takes a function as an argument. This function is the effect.- Second argument to
useEffect
is an array of values (dependencies). If any of the values change, the effect is re-run. If the array is empty, the effect is only run once, after the initial render. If you omit the second argument, the effect is run after every render which is not recommended.- Infinte re-renders can occur if there is a problem with the dependencies. E.g., if you forget to add the dependencies to the array or if you add a dependency that changes every time the component renders.
- You can have multiple
useEffect
hooks in a component. - Cleanup function can be returned from the effect. This function runs before the component is removed from the UI to prevent memory leaks.
- Example of
useEffect
hook with cleanup function:
import {useState, useEffect} from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => clearTimeout(timer);
}, [count]);
return <div>{count}</div>;
}
export default Example;
- In the example the cleanup function is used to clear the timer when the component is removed from the UI. Without it
the timer would continue to run and cause a memory leak. To test it without the cleanup function, remove
the
return () => clearTimeout(timer);
line. You will see in the console that the timer continues to run even after the component is removed from the UI (e.g., if you navigate to another page and then back to the page with the timer). - Typical situations where cleanup functions are used:
- Timers
- Event listeners
- Subscriptions
- the term subscription refers to a mechanism for receiving notifications when new data is available. To use subscriptions you would need to use a library such as RxJS which is not covered in this course.
useRef
is usually used to access DOM elements or to store mutable values.- in React, you should avoid using DOM-related APIs directly, such as
document.getElementById
ordocument.querySelector
- in React, you should avoid using DOM-related APIs directly, such as
useRef
can also be used to store mutable values that are not part of the state like the previous value of a prop or state variable. When the value changes, the ref will not re-render the component.useRef
returns a mutable ref object whose.current
property is initialized to the passed argument (initialValue
). For example:const someHTMLElement = useRef(null);
someHTMLElement.current
can then be used to access the DOM element.
- Continue last exercise. Create a new branch 'hooks' with git.
- Delete the hard coded
mediaArray
fromHome
component.- The data for the media items will be fetched from a static JSON file using
the fetchData function and the
useEffect
hook.
- The data for the media items will be fetched from a static JSON file using
the fetchData function and the
- Create a new state
mediaArray
and a functionsetMediaArray
using theuseState
hook:const [mediaArray, setMediaArray] = useState([]);
- The initial value of the state is an empty array.
- Create a new function
getMedia
that fetches the media items from the JSON file using thefetchData
function and updates themediaArray
state using thesetMediaArray
function.- Download test.json and save it to
the
public
folder.
const getMedia = async () => { const json = await fetchData('test.json'); setMediaArray(json); }; getMedia(); console.log(mediaArray);
- Download test.json and save it to
the
- Open the browser console and check that the media items are logged to the console. What do you notice?
- The
getMedia
function is called every time the component renders. Why?
- The
- The function should be called from a
useEffect
hook.- The function should be called only once, after the initial render. So the second argument of the
useEffect
hook should be an empty array (for now):
useEffect(() => { getMedia(); }, []);
- The function should be called only once, after the initial render. So the second argument of the
- Check that the media items are logged to the console only once. (Or actually twice, because the React is in development mode.)
- Use try/catch to catch errors in the
getMedia
function.- If an error occurs, log the error to the console.
- Git add, commit & push to remote repository
-
Continue last exercise. Now we get the data from the Media API.
-
Add and edit
.env.local
file to includeVITE_MEDIA_API=https://10.120.32.94/media-api/api/v1
environment variable.- APIDoc is here
- Works only in Metropolia's network or VPN.
- Open the APIDoc to accept the self-signed certificate.
-
Replace
test.json
withimport.meta.env.VITE_MEDIA_API + '/media'
ingetMedia
function. -
Next we want to display the owner's username with the media item. The owner's id is in the media item, but we need to fetch the username from the User API.
-
Edit
.env.local
to includeVITE_AUTH_API=https://10.120.32.94/auth-api/api/v1
environment variable.- APIDoc is here
- Open the APIDoc to accept the self-signed certificate.
-
Each media item has
user_id
property, which means that we need to make multiple requests to the API. We can use Promise.all to make multiple requests and combine the results to a single array. Example:const newArray = Promise.all(array.map(async (item) => { const result = await fetchData(url + item.id); return result; })); console.log(newArray);
-
Use the example above to get the user data for each media item and add it to the media item. Promise.all should return an array of media items with user data.
- You can do this in the
array.map
part of thePromise.all
function. - Then you can use object spread to add the user data to the media
item:
{ ...item, username: result.username }
.
- You can do this in the
-
Use the
setMediaArray
function to update themediaArray
state with the new array. -
Add the owner's username to the
MediaRow
andSingle
/SingleView
components.
- Run
npm build
ornpm run build
- Move build folder to your public_html
- Test your app:
http://users.metropolia.fi/~username/hooks
- Modify README.md. Change the link in
Open [X](X) to view it in the browser.
to point to the above link. - git add, commit & push to remote repository
- Submit the link to correct branch of your repository to Oma