In this task, we will add state management to the Posts app using Nanostores. Nanostores is a simple and fast state management library that provides a way to manage the state of your application without the complexity of other state management libraries like Redux.
Before we dive into adding state management with Nanostores, let’s understand the problem we are trying to solve. In our Posts app, we have components that need to share state and communicate with each other. For example, we define the posts
state in the Feed
component rather than the Posts
component because we need to pass the setPosts
function down to the AddPost
component (sibling of the Posts
component) to update the list of posts when a new post is added.
The Posts
itself passes the setPosts
function further down to its child, the Post
component. This is because the Post
component needs to pass this function to its children, the PostActions
component, EditPost
component, and PostActions
component. The PostActions
component further passes the setPosts
function to the DeletePostDialog
component where it is finally used to delete a post.
This kind of prop drilling can get complex and hard to manage as the application grows. We need a way to manage the state of the application in a more centralized and efficient manner. There are generally two solutions to this problem: using a state management library or using React’s built-in context API. We’ll focus on the former and use Nanostores to manage the state of our Posts app.
As we prepare to implement state management using Nanostores, let’s first understand the issue we’re trying to solve. In our Posts app, we have multiple components that need to share state and communicate with each other. For instance, the posts
state is defined in the Feed
component because it needs to be passed down to the AddPost
component (a sibling of the Posts
component) to update the list of posts when a new post is added.
This complexity propagates further: the Posts
component passes the setPosts
function down to its child, the Post
component. The Post
component then passes this function to its own children, including the PostActions
and EditPost
. The PostActions
then further passes the setPosts
function to the DeletePostDialog
component. This “prop drilling” can become unwieldy as our application grows.
We need a more centralized and efficient way to manage state in our app. There are two common approaches: using a state management library or React’s built-in Context API. We’ll focus on the former and use Nanostores to manage the state of our Posts app, simplifying our codebase and improving maintainability.
First, let’s install Nanostores in our project. Run the following command in your terminal: (Make sure your current directory is the web
directory)
Nanostores is a lightweight state management library that provides a simple and efficient way to manage the state of your application. It is designed to be easy to use and understand, making it a great choice for small to medium-sized applications. It has good TypeScript support and works well with several popular frontend frameworks, including React.
We must install the @nanostores/react
package, which provides React bindings for using Nanostores in React components.
The term “store” is used to describe the central place where the state of the application is stored and managed. In our case, we will create a store to manage the posts state. This store will contain the posts data and functions to update the posts data.
Add a new file called store.ts
in the web/src/lib
directory. This file will contain the store for managing the posts state. Here’s how you can define the store using Nanostores:
In this code snippet, we import the atom
function from Nanostores and define a new atom called $posts
. An atom is a reactive value that can be read and updated. Atom store can be used to store strings, numbers, arrays. We initialize the $posts
atom with an empty array of PostType
objects.
Here’s how you can use the $posts
store in your components:
Notice how we use the useStore
hook from @nanostores/react
to access the value of the $posts
store in our component. This hook subscribes to the store and re-renders the component whenever the store value changes.
In addition to the state, we also need to define actions to update the state. Let’s add actions to set, add, update, and delete posts in the store. Update the store.ts
file as follows:
Notice the use of the set
and get
methods on the $posts
atom to update and retrieve the value of the store. The setPosts
function sets the posts to the provided array of posts. The addPost
function adds a new post to the beginning of the posts array. The removePost
function removes a post with the specified id
from the posts array. The updatePostContent
function updates the content of a post with the specified id
.
In the next step, we will refactor our app to use the store for managing the posts state.
In this step, we will refactor our components to use the store for managing the posts state. We will replace the existing state management with the store actions we defined earlier. We start at the bottom of the component tree and work our way up.
DeletePostDialog
componentLet’s start by updating the DeletePostDialog
component to use the removePost
action from the store instead of the setPosts
function. Open the DeletePostDialog.tsx
file and make the following changes:
Notice how we removed the setPosts
prop from the DeletePostDialog
component and replaced it with the removePost
action from the store. Now, when the user confirms the deletion of a post, the removePost
action is called to remove the post from the store.
PostActions
componentNext, we go up the component tree and update the PostActions
component as it does not need to pass the setPosts
function anymore to the DeletePostDialog
component. Open the PostActions.tsx
file and make the following changes:
In the PostActions
component, we don’t need to pass the setPosts
function anymore. We updated the DeletePostDialog
component to use the removePost
action from the store instead of the setPosts
function. This simplifies the component and removes the need to pass the setPosts
function down the component tree.
EditPost
componentNext, we’ll update the EditPost
component, a sibling of the PostActions
component, to use the updatePostContent
action from the store instead of the setPosts
function. Open the EditPost.tsx
file and make the following changes:
In the EditPost
component, we replaced the setPosts
function with the updatePostContent
action from the store. Now, when the user saves the edited post, the updatePostContent
action is called to update the content of the post in the store. As a result, we don’t need the setPosts
function to update the posts state. So it is taken out from the component.
Post
componentNow that we have updated both the PostActions
and EditPost
components to use the store actions, we can simplify the Post
component as well. Open the Post.tsx
file and make the following changes:
Notice how we removed the setPosts
prop from the Post
component and updated the EditPost
and PostActions
components to use the store actions instead of the setPosts
function. This simplifies the components and removes the need to pass the setPosts
function down the component tree.
Posts
componentLet’s go up the component tree and update the Posts
component. This component no longer needs to pass the setPosts
function down to the Post
component. Moreover, it can directly use the store instead of receiving the posts
state and setPosts
function as a props. Open the Posts.tsx
file and make the following changes:
Notice we no longer need to pass the setPosts
function to the Post
component anymore. Additionally, we can use the post store directly in the Posts
component using the useStore
hook from @nanostores/react
. This simplification streamlines the component’s logic and eliminates the need to propagate the posts
state down to the Posts
component. We also utilize the setPosts
action within the useEffect
hook once the component is mounted and the post data has been fetched, effectively updating the posts data.
AddPost
componentIt is time to update the sibling of the Posts
component, the AddPost
component, to use the store actions instead of the setPosts
function. Open the AddPost.tsx
file and make the following changes:
In the AddPost
component, we replaced the setPosts
function with the addPost
action from the store. Now, when the user adds a new post, the addPost
action is called to add the post to the store. This simplifies the component and removes the need to pass the setPosts
function down the component tree.
Feed
componentFinally, we update the Feed
component, the parent of the AddPost
and Posts
components. The Feed
component does not need to define the posts
state anymore. Open the Feed.tsx
file and make the following changes:
In the Feed
component, we removed the posts
state and the setPosts
function. We updated the AddPost
and Posts
components to use the store actions instead of the setPosts
function. This simplifies the Feed
component and removes the need to manage the posts
state in the component.
Here’s a summary of the changes we made to refactor our app to use the store for managing the posts state:
DeletePostDialog
component to use the removePost
action from the store.PostActions
component to remove the setPosts
prop and use the store actions.EditPost
component to use the updatePostContent
action from the store.Post
component to remove the setPosts
prop and use the store actions.Posts
component to use the store and the setPosts
action.AddPost
component to use the addPost
action from the store.Feed
component to remove the posts
state and the setPosts
function.Running the app after these changes should work as expected, and you should see the same functionality as before. However, the code is now more maintainable and easier to understand.
We can further optimize our app by using the store for other shared state and actions. For example, we define the showAddPost
state and setShowAddPost
function in the App
component and pass these down to the components that need them. We can refactor this to use the store for managing the showAddPost
state and setShowAddPost
action. I’ll leave this as an exercise for you to try.
In this task, we successfully added state management to our Posts app using Nanostores. We refactored our components to use the store for managing the posts state, simplifying our codebase and improving maintainability. We learned how to define a store, create actions to update the state, and use the store in our components. We also saw how Nanostores provides a simple and efficient way to manage the state of our application without the complexity of other state management libraries. We encourage you to explore Nanostores further and experiment with other features it offers.