— May 30, 2023
In this article, we will compare Structura.js and Immer.js and explore their features, advantages, and disadvantages.
let keyword), an immutable state involves a state that should not be mutated (e.g.,
const keyword). As such, immutable states allow developers to write code that is more maintainable, predictable, and less prone to bugs.
let signifies that the variable can be reassigned a new value; while creating a new variable using
However, things start getting tricky when you introduce objects into the mix. Even if you create a new variable using
Immer.js, at its most fundamental level, allows you to work with immutable data structures by creating a draft state that you can modify as if it were mutable. The library then creates a new immutable state based on your modifications to the draft state. Here’s a basic example of using Immer to update an immutable state:
We take our
baseState and use Immer’s
produce method to update a
draft version of
nextState will result from
draft after performing our updates on
draft, thereby avoiding mutating
baseState. Immer provides a draft API that allows you to modify the state as if it were mutable. This makes working with immutable state incredibly convenient.
base state and
draft state, improving performance and reducing memory usage. Immer also allows you to work with nested data structures and ensures that any changes you make to them do not affect the original state.
Immer is a tried-and-true library and has a large developer community. So, if you run into issues using the library, there is bound to be a developer online who has run into the same issue and knows a solution. There are also useful resources to start learning immutability with Immer, available in their official documentation.
The main disadvantage to Immer is that it is not highly performant, especially when it comes to large objects. Under the hood, Immer uses
Object.freeze during runtime to make the object read-only, which leads to performance issues when working with large objects.
Nested objects take an especially large performance hit because
Object.freeze needs to be run at each level of the object. To address this, Immer has an entire resource page dedicated to improving performance while using the library.
Some edge cases require some workarounds. For example, Immer requires that you mark all custom classes as
immerable with a custom property. Immer also doesn’t support circular references (for example, when a property of the object refers to the object itself); the library only supports unidirectional trees.
Structura.js is a relatively new library designed to be nearly identical to Immer.js in terms of usability, but is much more performant. Though Structura.js and Immer.js share the draft API concept and produce a new state by updating a
draft object, the way they operate under the hood is fundamentally different.
Object.freeze during runtime to make the object read-only. Structura freezes objects at compile time with TypeScript using custom types. The performance cost of making an object immutable is reduced to zero because no work is done during runtime. It’s a fairly innovative concept. Here’s an example of using Structura to update an immutable state:
Structura and Immer handle immutability similarly with the
produce method. The only difference is that the
baseState in the Structura example is marked as frozen by being assigned the
Freeze type. This tells Structura to disallow any updates to
baseState outside of the
The main advantage of using Structura.js is, of course, the performance benefits. It’s lightning-fast compared to libraries like Immer, which handle freezing during runtime. In fact, Structura claims to be upwards of 22x faster than Immer. Structura also handles some edge cases other libraries struggle with, like circular references.
According to its official documentation, Structura.js is advantageous for the following reasons:
The developer community around Structura is very small, and the library is still in alpha. This means it is less mature and stable than established libraries like Immer. Because of the library’s small community and its somewhat underdeveloped documentation, there may be some trial and error when using the library, and you may have to be more self-sufficient in resolving issues you run into.
Now that we’ve covered what makes Structura unique from (and similar to) Immer, let’s take a look at how we can use it in practice. To use Structura.js, you need to include the library in your project. You can download the library from the official GitHub releases page or install it using a package manager like npm or Yarn:
You can also include the library in your web projects via a CDN, such as JSDeliver:
Structura provides the production function that allows us to create and modify immutable data structures. Here is an example of how to use
In this example, we have an object called
baseState that contains a list of
todos. We then use the
produce function to create a new object called
nextState that represents the modified version of
produce function takes two arguments: the
baseState and a
producer function. Inside the
producer function, we modify a
draftState by adding a new
todo and marking the first
todo as completed.
Structura.js then produces
nextState from the mutations to the draft state within the
producer. The key thing to note is that we are modifying the
produce. Structura.js will create a new immutable version of the
state object for us!
One neat side-effect of working with immutable data structures is that you maintain the ability to record exactly what parts of the states were changed, and in what way. These records are called
patches, and can be very useful for keeping a space-efficient history of the changes made to your state.
For example, if you want to redo an edit, you can edit your state according to the patch that produces the next state. If you want to undo an edit, you can use the inverse of the previous patch. Structura provides built-in functionality for working with patches.
produceWithPatches function, you can gain access to the new state, as well as the patches and inverse patches. Here’s an example of how you can use the
patches functionality in Structura. First, produce the
nextState and keep track of the
patches (and inverse
Now, if we apply those
patches to the original starting point
baseState, we should end up with the same
nextState. This is a redo functionality! Here it is:
If we apply the inverse
nextState, we should end up back to our original starting point
baseState. This is the undo functionality:
It should also be noted that Immer.js also provides support for patches, though you must explicitly enable the feature before usage. Consult their documentation for more information.
If performance is of utmost importance for your application, consider using Structura.js. Unless you use the utility methods provided by the library, it adds zero overhead to your application because it does not interact with your data during runtime and will be faster than Immer in almost every situation as a result.
With that said, if you need a production-ready library with a large community, consider using Immer.js. It is a well-established library, unlike Structura.js, it is stable and ready for use in production.
Don’t miss out on the latest content! Join over 3k+ devs in subscribing to Instructive.dev.
Join the mailing list