Introduction
How do you use
useState()
anduseEffect()
hooks effectively?Understand the concept of lifting state
Manage state and pass data between several components.
Managing Internal State - useState()
The useState()
hook is crucial for saving state variables within your components.
It allows you to change the state using a setter function.
Here's a simple example:
const [state, setState] = useState<number>(0);
State Variable: Represents the current state.
Setter Function: Alters the state.
This hook observes the state of our component and triggers mutations based on certain conditions.
Structuring Components
Good practice*: creating a folder for our components: "Components".*
Each component should represent a ✨ specific ✨ part of our data structure:
Single-Responsibility Principle (SOLID Principles)
For instance, if you have a user object, you can create components for different parts of this object, like User
, UserAddress
, and UserCompany
.
User Component
Let's create a User
component with props:
const User = (props: Props) => {
return (
<div>
<h2>{props.username}</h2>
<p>Email: {props.email}</p>
<p>Phone: {props.phone}</p>
</div>
);
};
Now, define our data structure for the props and map them accordingly:
interface Props {
id: string;
email: string;
username: string;
phone: string;
}
Each component should have a wrapper node, like a <div>
or a React Fragment.
Handling Side Effects - useEffect()
The useEffect()
hook is used for performing side effects.
Example: fetching data from an API.
We now, create a folder for our "Services" containing our logic for the API call.
For example UserService.ts
Here's an example of making an API request using Axios.
import axios from 'axios';
import { UserModel } from '../models/UserModel';
export const getUserInformation = async (): Promise<UserModel[]> => {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
return response.data as UserModel[];
};
Now, we will use this cal in our UserList component:
export default function UserList() {
const [usersResponse, setUsersResponse] = useState<UserModel[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [hasError, setHasError] = useState<boolean>(false);
const [isFinished, setIsFinished] = useState<boolean>(false);
useEffect(() => {
setIsLoading(true);
setHasError(false);
if (!isFinished) {
fetchUserData()
.then((response: UserModel[]) => {
setIsLoading(false);
setUsersResponse(response);
setIsFinished(true);
})
.catch((error) => {
setHasError(true);
setIsLoading(false);
return error;
});
}
}, [isFinished]);
return (
<div>
{usersResponse.map(user => (
<User name={user.id} id={...user} />
))}
</div>
);
}
Some notes:
Dependencies: The empty array
[]
ensures the effect runs only once after the initial render.Cleanup: If the effect has dependencies, ensure you provide a cleanup function to prevent unnecessary re-renders.
Lifting State Up
When components need to share state, lift the state up to the nearest common ancestor.
Idea: The parent component manages the state and passes it down as props.
Example
Let's say UserList
is the parent component:
javascriptCopy codeconst UserList = () => {
const [users, setUsers] = useState([]);
// Fetch users and set state...
return (
<div>
{users.map(user => (
<User key={user.id} {...user} />
))}
</div>
);
};
The User
component receives props from UserList
:
javascriptCopy codeconst User = ({ id, name, username }) => {
return (
<div>
<h2>{name}</h2>
<p>{username}</p>
</div>
);
};
And that's how we understand lifting the state up through components.
Structuring Smaller Components
Keep your components small and reusable.
For example, instead of combining UserCompany
and UserAddress
into one component, keep them separate:
const UserCompany = ({ company }) => {
return <span>{company.catchPhrase}
</span>;
};
By doing this, we adhere to good software architecture principles, making our components more Manageable and Testable.
Conclusion
Using useState()
and useEffect()
, along with lifting state up, allows us to manage and share state across components efficiently.
Keeping our components small, maintainable, and reusable.
This approach not only improves performance but also makes our codebase easier to understand and work with.