In the last few projects I had the opportunity to work with Svelte. Due to how the project was setup, I did use Sveltejs, without Sveltekit.
I’m glad of the opportunity to have used Svelte stand-alone, because it helped me to grasp the basic concepts of the framework/library.
I like how Angular allows you to structure the code, and the support out of the box for complex Forms. I love the Reactive code, and I’m a fan of the “Observable Data Service” pattern.
It looks like Redux, but much easier to work with.
On the other side, I did not like the React way, and the burden of 3rd party libraries, hooks, and that you need libraries like react-form-hook to manage Forms.
Then, to have a centralize state in the application (or per-component), you need Redux or one of the thousand similar libraries built for React implementing the Redux concept.
So I tried to mix all my previous experience, and struggled to find a good way to achieve my goals.
The Svelte documentation is too simplistic, in the examples they build too-simplified cases.
Very few blog posts or other source of information.
It took a while to find a good implementation that could satisfy me:
– dummy components, no business logic or application state. A component needs only to consume the state and build a UI over the actions.
– a reactive store, that exposes actions and a state. The store could expose computed/derived properties. The store should take care of the “loading/busy” state, error messages due to failed backend calls, etc. The store hence calls methods in a service that implements fetch/api methods.
– form and form validation in a simple way
– unit-testable business logic, and in this case, the store
I think I manage to find a good way to achieve my goals:
– A reactive store that exposes a state, derived state and methods.
– The component uses the state to show data to the user, and calls methods per user action.
– Form validation using native validity
– More complex form validation using Yup
– Everything easily unit-testable with Vitest
You can find a repo using Vite, TypeScript 5, Svelte 4, ESlint, Vitest, and Yup here: svelte-example
The “example app” implements the standard “counter”, but instead of using directly a writable store in the component, it separates the logic to a dedicated store, in an “enterprise” way of build apps.
The store expose a state with:
– isLoading: a backend call/async process is in progress
– errorMessage: keep track of the latest error to be shown to the user
– counter: the actual counter value that can be incremented or decremented
– targetValue: a value that – when equal to counter – displays a notification to the user (via a derived state)
The store exposes some methods to allow to change the state:
– init: to reset the state, implements basic dummy logic to show how to handle with the “errorMessage” concept.
– increment: increment the value, but simulate also a 1sec backend call to dimostrate how to manage the “isLoading” state and disable user inputs (and show a kind of spinner).
– decrement: decrement the value, same as increment
– hasTarget: a derived state that expose if the counter is equal to “targetValue”.
The Component shows how to deal with:
– reactive store state, and you can have as many named-store is needed, and share state between components, as they are singleton.
– reactive derived state, very powerful that you can split the “raw data” from the “computed” logic.
– browser native form validation: make use of required, min, max, pattern, etc built-in features
– more complex validation using Yup: no limits to the validation thanks to Yup (and unit testable)
– how to apply css styles to inputs with invalid state: making use of built-in “:invalid” css state
– svelte action to react to validity state changes and apply a custom class. Useful to integrate with Fronted Toolkits like Bootstrap, as example.
I think this should cover 90% of what usually is needed in an enterprise application.
The downside of this approach is that the state is emitted for each state change, making the UI flickering.
This is also because in a method you could also update the state (and emitting a new one) many times. A common pattern is to set “isLoading = true”, do async work, and emit a new state with also “isLoading = false”. This means we have at least 2 new state emitted. If you need to compute a derived state (as the store is computed sync) to be able to continue within the method (via “state = get(store)”), then you emit a new state more times per-method.
But in my case it was a good trade-off, that allowed me to keep the core code very simple and with low complexity.
However if this is not acceptable, then I think a good solution could be to have more fine grained stores. i.e. to divide the store in more sub-stores, splitting a “big state” in “smaller states”, with the same approach, just replicated.
Any feedback is appreciated.