Lifting State through components using Props, useState(), and useEffect()
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.
Subscribe to my newsletter
Read articles from Olga Nedelcu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Olga Nedelcu
Olga Nedelcu
I'm a community-minded engineer based in Berlin, originally from Romania, and I grew up in sunny Spain. I combine the best of both communication and technical skills, with experience in both the engineering world and the business side of things. I'm proficient in a wide range of technologies like JavaScript, React.js, Redux, TypeScript, Node.js, HTML/CSS, Styled Components, SASS, and REST APIs. I love going beyond just writing code by sharing knowledge with my team through Communities of Practice (COPs), Employee Resource Groups (ERGs), and detailed technical documentation. My main interests are in frontend frameworks, web standards, accessibility (A11y), and Clean Code. My passion for programming started in high school when I built my first website. Since 2019, I've been working as a professional software developer, thriving in agile and iterative environments. Nearly five years later, I'm still excited to dive into code and collaborate with my colleagues. I'm very open and friendly, good at turning technical concepts into easy-to-understand information for everyone.