Cross cutting concerns¶
Layouts¶
If you have state that is shared between pages, simply put it in your layout.
For example. In the generated application.json.props
path = request.format.json? ? param_to_dig_path(params[:props_at]) : nil
json.data(dig: path) do
json.temperature "HOT HOT HOT"
yield json
end
In the above, every page that gets rendered will have temperature
as part of
the page response.
Partials¶
We can also use partials to extract crosscutting concerns. For example, a shared header:
app/
|-- controllers/
|-- views/
| |-- shared/
| | |-- _header.json.props
| |-- posts/
| | |-- index.js
| | |-- index.json.props
| | |-- index.html.erb
| |-- comments/
| | |-- index.js
| | |-- index.json.props
| | |-- index.html.erb
By design this results in duplicate JSON nodes
across our pages
slice:
{
pages: {
"/posts": {
data: {
header: {
email: "foo@foo.com"
}
}
},
"/comments": {
data: {
header: {
email: "foo@foo.com"
}
}
},
}
}
Advanced functionality¶
For most cases where you don't have to mutate your store, using layouts or partials would be good enough. Its a fine tradeoff for simplicity.
Sometimes we have global concerns that we'd like to keep updated. This can be for across pages when navigating or if we'd like to perform client-side updates. For example, if we're showing a shopping cart quantity on the header, we want to keep that updated as we navigate back, and when updating line items locally.
For this, Superglue has fragments and Redux slices.
Hint
You may not need to use fragments and Redux slices. For some apps, the only
slices you'll ever need is the generated flash.js
slice that comes with the
install step.
Fragments¶
A fragment in Superglue is any props_template block with given name:
Now whenever we encounter a fragment from a new visit
or update a fragment using remote
,
Superglue will dispatch an updateFragment
action.
- See reference
for
updateFragments
That's not a very useful thing by itself, but when combined with Redux toolkit createSlice and useSelector, it offers a way to easily build global concerns.
Slices¶
Whenever a fragment is received or updated, a UPDATE_FRAGMENTS
action is
dispatched with the value. You can return that value as your state to
keep your slice updated as the user navigates.
For example:
import { createSlice } from '@reduxjs/toolkit'
import { updateFragment } from '@thoughtbot/superglue'
export const cartSlice = createSlice({
name: 'cart',
extraReducers: (builder) => {
builder.addCase(updateFragments, (state, action) => {
const { value, name } = action.payload;
if (name === "cart") {
return value
} else {
return state;
}
})
}
})
Then somewhere in a component you can useSelector:
And as this is just a normal Redux slice, you can also add custom reducers to the mix for client-side updates.
initialState¶
You can render your slice's initial state in the slices key
of the page
object, it'll be merged with the initialState
passed to your buildStore
function in your application.js
Other actions¶
Aside from UPDATE_FRAGMENTS
, superglue comes with other actions that get
dispatched during lifecycle events that you can make use of. The flashSlice
that was generated with the installation is a good example of this.
To higlight a few:
BEFORE_FETCH
- Action created before a before a fetch is called.
BEFORE_VISIT
- Same as above, but called only for a visit
action.
BEFORE_REMOTE
- Same as above, but called only a remote
action.
SAVE_RESPONSE
- Whenever a page response is received.