Why Would I Use An Action For Anything Other Than Form Submissions?
I’m struggling to understand why I would use an action for anything other than handling a form submission.
Actions give you access to
FormData
, which simplifies working with forms. So, I can see a clear use case with forms.
But why else would I use an action? Let me try to add some context to my confusion.
The solid docs give the following example.
I can use an action to do the following
BUT, I could also use a regular async function to achieve the same outcome in fewer lines of code.
Additionally, the docs say:
"after submitting data the server sends some data back as well. Usually an error message if something failed. Anything returned from your action function can be accessed using the reactive action.result property. "
BUT, I can also use a try/catch block in a regular async function to return an error message.
So, what's the point of actions outside of working with forms?
Am I missing something? Or are actions just an alternative way to do what regular async functions can do?
Thanks,
Chris16 Replies
They have router knowledge so it can revalidate the data that was changed in the action
GitHub
GitHub - solidjs/solid-router: A universal router for Solid inspire...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
And now single flight mutations are supported
https://github.com/solidjs/solid-start/releases/tag/v0.6.0
GitHub
Release v0.6.0 - Take Flight · solidjs/solid-start
Listening carefully to the feedback and reflecting on the second month of beta 2 we have prepared this release which improves on a lot of the usability around Start itself. This is less about featu...
@peerreynders and @brenelz couldn’t I also do that with
createResource
and an async function?
Additionally, couldn't I use useNavigate
for redirection?by default when an action succeeds, it will invalidate all
cache
functions, which is more powerful than defining your fetchData
, then wrapping it in updateData
where you have to manually call refetch
on each resource you care about.
sure, but then you've got another need for that wrapper function that now must be defined inside a component (since useNavigate
requires access to router context), whereas using an action lets you issue the redirect inline with everything else you need to doTL;DR:
cache
, createAsync
, and action
are largely about moving the responsibility of loading and aggregation of page data out of the individual component into the router which has more knowledge about the whole page than each individual component.
As a REST-osaur I see coordinating the loading of the page-level data based on the client side URL as one of the router's responsibilities which mirrors the server preparing of “Resource's Representation of State” based on the requested URL.
Long Story: In 2013 React's “Just the V in MVC” didn't want to deal with routing concerns. Shortly thereafter Redux's Flux) implementation was the first adoption of a practice of moving data loading out of the components and instead have them largely “project” application state instead.
Ultimately that approach fell out of favour due to the inconvenience of maintaining the actions and selectors (while not taking advantage of distinguishing between essential state as indicated by the client-side route/URL and ephemeral state which only has short term, session specific relevance).
Fast foward to 2022 When To Fetch: Remixing React Router; i.e. fetch before you render, rather than fetch because you render. Now RSCs, being dogmatically component-oriented, still downplay server waterfalls, again de-emphasizing the importance of leveraging the router's knowledge.
Some of this could be explained by React Native culture.
With native apps there is no notion of a URL which demarcates essential from ephemeral state because there is no SSR and it isn't possible to capture navigation state which can be bookmarked or shared with others.
But in the web world URLs have significance so you may as well milk them for all they are worth. In the case of the Router the URL determines which data is going to be needed so you may as well start loading it even before you render.Meta Developers
YouTube
Hacker Way: Rethinking Web App Development at Facebook
Delivering reliable, high-performance web experiences at Facebook's scale has required us to challenge some long-held assumptions about software development. Join us to learn how we abandoned the traditional MVC paradigm in favor of a more functional application architecture.
Real World React
YouTube
When To Fetch: Remixing React Router - Ryan Florence
Recorded live at Reactathon 2022. Learn more at https://reactathon.com
When To Fetch: Remixing React Router
We've learned that fetching in components is the quickest way to the worst UX. But it's not just the UX that suffers, the developer experience of fetching in components creates a lot of incidental complexity too: data fetching, data muta...
So while
createAsync
makes it look like the component is loading the data, the intention is do so over a (router's) cache
d loader/value which gives the router just enough control to help orchestrating the page data loading. The action
s in turn can invalidate the cache
s so that the router can trigger the necessary updates which once they settle will propagate to the dependent createAsync
s.
With createResouce
you have to trigger the refetch directly on the resource and unless that resource is managed by a context it is specific to a component. cache
encourages you to "pull out" the fetch from the component and into the router.
Now multiple components can depend via createAsync
on the same cache
d value while any other component can invalidate the core cache
value with an action; consequently all the dependent createAsync
signals will update once the cache
value is reloaded.Thank you for your very thorough response.
I think I’m struggling here for the following reason:
I learned about full-stack web dev the traditional way using a backend like Express or Go. I’m trying to draw parallels between traditional web dev and web dev with meta frameworks and I can’t see where Actions fit in. Let me explain using three simple examples. Then I'll explain why I'm confused.
EXAMPLE 1:
I’ll start with what I would consider the Web Server or UI routes.
Using Express, here’s how you might handle a simple static Home Page route.
The equivalent of this with Solid and Solid Router might look like this:
EXAMPLE 2
If I want a dynamic page I can use the “Handler” to fetch the data.
Using Express that might look like this:
In Solid that might look like this:
One of the benefits of SolidStart is it adds file-based routing with the <FileRoutes /> component. This lets me define routes in a folder rather than defining each Route inside a Routes component. SolidStart lets me define both UI routes and API routes.
EXAMPLE 3:
Next let’s consider API Routes. Here’s a simple example with express for adding data to a db:
In Express that might look like this:
In SolidStart that might look like this:
With these three examples, I can do most things I need to do in web dev. I can serve static and dynamic pages, I can run async functions for data fetching, and I can run code on the server with API routes for doing “server” things like db interactions and talking to stripe.
So, where do Actions fit in? The docs say that Actions are just POST requests, so why do I need them? Can’t I just use a POST request in an API route?
I apologize if the answer to this seems obvious, but I’m pretty new to this stuff. There’s a good chance I’m missing a fundamental understanding of something.
Both you and @brenelz mentioned moving the responsibility of loading and aggregating page data out of the individual component and into the router, but my understanding of this concept is weak, and I can’t draw a parallel to what this would look like in an Express app.
You said:
I see coordinating the loading of the page-level data based on the client side URL as one of the router's responsibilities...
How do I do that in Solid? And, to expand upon my three examples above, how would I do the equivalent in an Express app?
Wait... does the
action
function in a UI route represent a POST
request to that route? And does moving data loading to a POST
request for a route move the responsibility of loading and aggregating data out of the component and into the router?
So assuming my path is routes/mycomponent...
does theNo. In SolidStart they are used with server functions, so they are essentially RPC calls where Start handles the serialization and fetch for you. By itself it doesn't actually interact with the router but the router's data API does give it the opportunity to do so if you choose to. What happens is that anaction
function in a UI route represent aPOST
request to that route?
addData
API route is replaced with an addData action
- no need for separately coded API routes.
The action can invalidate the cache
d data to let the router know that it's stale.
Based on the active createAsync
dependencies the router will then decide to perform a fetch to refresh the cache which in turn will propagate to the createAsync
signals once the fetch has settled.
Preloads can similarly benefit from the route's cache
management by warming the cache
even before the components need it.
I think the issue here is that you are equating
handleAccountPage
to a component or worse components to miniature pages. SolidJS is attempting to move "beyond components".
The idea is to initiate the client side fetch as soon as we know where we are navigating to (which precisely identifies the data requirements), and which router cached data is stale (because the action should be aware of its consequences), but before even thinking about components.
The goal is to have fetched data drive the components rather than having the components drive data fetching largely because the latter provides a worse UX.
Think of createAsync
as the component identifying it's data requirements based on the routes cache
d values. Once a component is rendered to the DOM the createAsync
signal will keep it in sync whenever the router refreshes the cache
d value. Then any action
anywhere in the client side app can notify the router which cached values are affected by it so that it can refresh them as necessary.
Cache updates automatically trigger transitions so there will be paint-holding under the suspense boundaries which is generally preferred to spinners provided you can update within 1s.Chrome for Developers
Paint Holding - reducing the flash of white on same-origin navigati...
A quick overview of paint holding. A Chrome feature for reducing the flash of white on same-origin navigations
From the docs
In SolidStart you would use
because the is no need for an API route.
The
cache
wrapper lets the router rerun the "fetch" whenever it needs to refresh the value.
Now inside a component you consume this with createAsync
The user
signal will now get the latest value whenever the router reruns that "fetch" registered with the cache.
Now perhaps in an entirely different component:
"use server"@peerreynders You’ve been incredibly helpful. Thank you!
I think I’m starting to get this. I also watched Ryan’s video, which helped: https://www.youtube.com/watch?v=RzL4N3ZavxU&t=7078s
You said:
*The goal is to have fetched data drive the components rather than having the components drive data fetching largely because the latter provides a worse UX. *
That makes a lot of sense.
Taking things one step at at time here, I’ll start with loading data. Here’s my understanding of how to load data OUTSIDE of the component. Does this pattern look correct?
Ryan Carniato
YouTube
SolidStart: The Shape of Frameworks to Come
Join me to take a look at what SolidStart is shaping up to be. I will be going through a full tour of the framework to show you what it is, what you can do with it, and how it changes things.
[0:00:00] Intro
[0:05:30] The Shape of Frameworks to Come
[0:18:00] A Classic Client-Side Solid App
[0:30:45] Simple Todo App with a Router & a Form
[0:45...
Here's the output on the todos page.
And here's the todos data that loaded on hover from the home page:
I think now the
load
function exists to warm the cache even before the components load.
The original beta had an explicit route loading mechanism however more recently it became clear that component loading is irresistible to a significant segment of the developer audience.
Consequently the cache/createAsync/action API emerged to create the opportunities for route loading by anchoring the cached values within the router, while createAsync
still created the component loading feel while actually hooking into the router's data loading mechanism.
A lot of the reasoning that went into the API was discussed in
https://youtu.be/8ObxzMSIqKA
and to some degree
https://youtu.be/veKm9MDVVg8
So far you've set up the reactive graph to supply your components with up-to-date todos.
The next step is to add actions into the mix to update the todos which then invalidate the cache to compel the router to reload them from the source.
(In more advanced scenarios you then use the action submissions to provide an optimistic view even before the cache has updated, by replacing stale values with optimistic ones - it's not something that happens automatically but relies on you deftly orchestrating the blending of up-to-date and optimistic data - that's what is going on with the example todomvc).MJ (@mjackson) on X
Would you rather have your data fetching associated with the current route or with a component?
Twitter
Ryan Carniato
YouTube
Evolving Isomorphic Data-Fetching
New constraints have been leading to a complete rethinking of how we handle isomorphic data-fetching in JavaScript frameworks. Truthfully I don't have the full answer yet. Join me as we explore this topic understanding the constraints, learning from history, and attempt to design what the architecture of the future looks like.
0:00] - Preamble ...
Ryan Carniato
YouTube
Server Functions & Server Actions
What's the big deal with Server Functions/Actions. Are they the same thing? What do they do? We will explore the topic today and look at how these are shaping the future of fullstack applications.
[0:00] Preamble
[7:30] Remix & Why Routing is Important
[17:00] The History of Remote Procedure Calls
[26:30] Solid's Road to Modern Server Actions
[...
GitHub
GitHub - solidjs/solid-router: A universal router for Solid inspire...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
Amazing! Thank you. I’ll work on actions tomorrow to see if I can figure those out. I’ll report back. Thanks again!