Introducing Preact: The 3KB React Alternative
Preact, a fast 3 kilobytes alternative to React. It uses the same familiar JSX and hooks API but sticks to the bare minimum in order to be both small and powerful. This is a major selling point since the new wave of frameworks such as Solid or Svelte rely on much less code sent to the browser, and libraries such as Astro or Qwik are proof that the general direction of the front-end space is to deliver fast, HTML-first apps with great performance and awesome immediate time to interaction metrics.
With its extremely small size, Preact is a viable option in today's development environment. And in this video, we'll take a look at its core features, its signals-based reactivity, and more, while building a cool little language learning app.
Project Setup and Structure
Setting up the project is straightforward using the Preact CLI. Run the following command in your terminal, and you will end up with a basic project we can use. As a quick FYI, we'll be using TypeScript since it has native support in Preact. Regardless of the project type, project size, or team experience, I believe you should always use TypeScript in your projects because its benefits outweigh any possible drawbacks.
So let's take a quick look at the project structure. Like in any other modern framework, the components are Preact's main building blocks. The library comes with a lightweight router as well, and we'll let any business logic or backend API calls in the services directory. As you'd expect, any public static files can be stored in the assets folder, and general CSS rules can be saved in the style directory.
Preact Components and Routing
If you are familiar with React or Solid, the component structure should look familiar. We'll be using functional components since these are pretty much the status quo at this point. Inside the function, you can define state using Hooks and you'll need to return this HTML-plus-JavaScript mix called JSX as your component template. We'll take a look at all these in detail in just a second.
In the meantime, let's quickly discuss routing. This is a concept already solved in single-page applications, and in Preact, we have access to good tooling for it. Always keep in mind to model and architect your app around routes, since your users expect to be able to reliably share links in a manner in which our app will always reflect the URL. We are building a small application, so we'll just add mappings for the root and the words path. Keep in mind, however, that the Preact router is more flexible than that and allows you to pass path variables, do redirects, and more.
Global State Management with Context API
Next, let's look in detail at managing state in our application. Again, this is a meta topic in front-end development. There are tons of libraries and approaches to handle data flow, side effects, and reliably update and consume data throughout the app. Preact, just like React, comes up with the Context API, which allows you to easily work with shared data. In my experience, the simplest approach works the best for this problem, and this mindset is reflected in the third-party state management libraries where in the last years everybody is trading off features and architectural constraints for simplicity.
In the shared context, we are adding a theme mode to style the app accordingly in a light or a dark theme, and the list of known words, to which we'll come back in a couple of minutes. Back in the app.tsx file, we are going to define some state to be linked to the context. Note that we are using hooks for most of our work. Hooks were a great addition to React, and then they were ported in Preact version 10 as well. They allow you to work in a more functional manner and at this point replace the class-based components completely. The Context API will allow you to fetch the state we defined anywhere in the component tree, but for this to work, we'll need to wrap our app in a Context provider. If you are not familiar with all these, know that the alternative would be to pass all this information down as component properties, which can easily get out of hand and turn into a boilerplate hell.
Next, we'll jump into the header component and see that we can easily retrieve context data by calling the useContext hook. Other than this, there are no major points of interest here. Just know that I am using the router Link component to correctly define links in our single-page application. Also, in the JSX, I'm conditionally rendering a theme mode button using the JSX approach. If you are coming from other libraries which are offering special components or directives for conditional rendering, I get that this might look a little bit weird. To wrap up the header implementation, let's register an onClick event handler which will allow the user to toggle the dark mode on or off. This is a rather naive implementation, and in a real project, you might want to persist such user decisions in the local storage or even better, on a back-end server.
Fetching Initial Data with useEffect
It's time now to jump in the today.tsx file and work on something a bit more interesting. We'll start by defining some state values to work with. As a quick FYI, note that I'm using CSS modules in this project. Since styling is not really a topic for this video, I'm not getting into any such details, but you can check out the full app implementation on the GitHub link I'm adding in the description.
Back to the code, the useEffect hook is the main mechanism Preact offers to hook into some component lifecycle methods and run code whenever some state or property values are changing. The important aspect here is the dependency array. Whenever this array is empty, the effect will run only once when the component is mounted in the DOM. You probably notice that the code we are writing is 100% compatible with React. This is the main selling point of the Preact library; even though it offers so much flexibility, it still is extremely small in size.
Enhancing Reactivity with Preact Signals
A new exciting addition to the library I want to mention here are Signals. For 1.5 kilobytes more, you can choose to add them into your project and unleash more reactive capabilities. Signals became really popular in recent years because of Solid.js, and they are the most efficient way possible to automatically update UI components. They don't suffer the same constraints state values are facing; they can be computed or held with better managing global state.
Implementing the Core Application Logic
When the component is mounted, using the newsdata.io API, we'll fetch a random news story. Then we are splitting the title and the description of the story and we are rendering each word individually in the page. The whole idea here is that whenever the user opens the app, he'll be able to read a new story about the current events. If any of the words is unknown, he'll be able to click on it and read some explanations, listen to an English speaker saying the word, and then save this word in his list.
Okay, so we fetched a recent news event and we are rendering it to the DOM. Apparently, Elon Musk's old photos being sold is newsworthy. Anyhow, getting back to the code, let's use the Lingua Robot API to fetch the definition whenever a word is being clicked. Just like with the styling, I'm not getting into any details about working with third-party APIs, since these are just some basic fetch calls with some TypeScript annotation for clear usage.
What I want to mention though is the useCallback hook, which we'll be using to wrap our story fetching call. This is a good practice because it avoids redefining functions whenever the rendering process is triggered. Preact is React-based, so it follows the same virtual DOM and rendering approach where the entire function component is re-executed whenever props or state values are being changed.
Building a Dynamic Modal Component
Okay, once we have a word definition added in the state, we can conditionally render a small UI modal to display all these details. We'll jump into the word modal file to do that, and this implementation is just revisiting what we've already discussed. Some props are being sent to the component, and internally we are simply rendering this information.
Next, we want to retrieve the audio source file from the word object and link it to an HTML audio element. After we define the state value, we'll use the useEffect hook again with the word variable in the dependency array. Whenever the modal component will receive a new reference of the word property, it will run the effect again. Okay, so it looks like most of our implementation is done.
Persisting Data and Updating Context
and we just need to allow users to save new words in their list. I am storing all this information in the local storage, but in a real application, you'd probably persist all this information in a database or in some sort of backend as a service solution. As a side note, I really encourage you to take a look at backend as a service providers, since they are now doing what Preact is doing to React: they come up with simpler, more efficient ways to solve specific problems. At the end of the day, as developers, this should be our main goal: quickly build reliable and maintainable software using smart tools which simplify the whole process.
So whenever save is clicked, we are updating the local storage entries and the context value as well. As a result, when the list is updated, the header word counter will reflect the new array length.
Final Touches and Conclusion
To wrap things up, let's quickly work on the words route. This is what you'd call a dumb or a presentational component, since its only purpose is to display some data. We'll iterate over the context words and render them to the DOM. And with that, our small app is completely done and ready to use.
While developing it, we got a chance to work with Preact and we saw that most of the things you are able to accomplish using one of the heavy, popular frameworks can also be done with a much smaller library and with better performance and network usage. With such alternatives, I believe it's harder and harder to justify unnecessarily delivering megabytes of data to the client, and all the front-end space will start to shift towards a thinner, more robust way of doing things. If you've made it this far, please consider liking this video and subscribing to the channel. Thank you for watching.