diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml new file mode 100644 index 00000000..9ac70b99 --- /dev/null +++ b/.github/workflows/publish_docs.yml @@ -0,0 +1,43 @@ +name: Publish docs +on: + push: + branches: + - main +permissions: + contents: write +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: npm install + working-directory: ./superglue + run: npm install + + - name: Build typedoc + working-directory: ./superglue + run: npx typedoc + + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: pip install mkdocs-material + - name: Build mkdoc + run: mkdocs build --site-dir ./_site + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 1029b455..00000000 --- a/docs/README.md +++ /dev/null @@ -1,126 +0,0 @@ -
- Logo -
- -# Superglue - -Use classic Rails to build rich React Redux applications with **NO APIs** and -**NO client-side routing**. - -[![Test superglue_js](https://github.com/thoughtbot/superglue/actions/workflows/build_js.yml/badge.svg)](https://github.com/thoughtbot/superglue/actions/workflows/build_js.yml) -[![Test superglue_rails](https://github.com/thoughtbot/superglue/actions/workflows/build_rails.yml/badge.svg)](https://github.com/thoughtbot/superglue/actions/workflows/build_rails.yml) - -Superglue makes React and Redux as productive as Hotwire, Turbo and Stimulus. -Its inspired by Turbolinks and designed to feel like a natural extension of -Rails. Enjoy the benefits of Redux state management and React components -without giving up the productivity of Rails form helpers, UJS, tag helpers, -flash, cookie auth, and more. - -## Caution - -This project is in its early phases of development. Its interface, behavior, -and name are likely to change drastically before a major version release. - -### No APIs - -Instead of APIs, Superglue leans on Rail's ability to respond to different -[mime types](https://apidock.com/rails/ActionController/MimeResponds/InstanceMethods/respond_to) -on the same route. In a Superglue application, if you direct your browser to -`/dashboard.html`, you would see the HTML version of the content, and if you -went to `/dashboard.json` you would see the JSON version of the exact same -content down to the footer. - -The end result would be something like this: - -![No Apis](https://thoughtbot.github.io/superglue/images/no_apis.png) - -### Powered by Classic Rails -Superglue leans on Rails. Features like the flash, cookie auth, and URL -helpers continue to be useful. Here's a look at the directory structure of a -typical Rails application with Superglue. - -```treeview -app/ -|-- controllers/ -|-- views/ -| |-- dashboard/ -| | |-- index.js # The React page component -| | |-- index.json.props # The json for the page component -| | |-- index.html.erb -``` - -### PropsTemplate -Powering the JSON responses is PropsTemplate, a digable JSON templating DSL -inspired by JBuilder. With PropsTemplate you can specify a path of the node you -want, and PropsTemplate will walk the tree to it, skipping the execution of nodes -that don't match the keypath. - -![No Apis](https://thoughtbot.github.io/superglue/images/props_template.png) - -### All together now! -Superglue comes with batteries that bring all the above concepts together to make -building popular SPA features easy, painless, and productive. - -#### SPA Navigation -A popular ask of SPAs is page-to-page navigation without reloading. This is -easily done with Superglue's own UJS attributes inspired by Turbolinks: - -```jsx - -``` - -The above will request for `/posts` with an `accept` of `application/json`, and -when the client receives the response, swap out the current component for the -component the response asks for, and `pushState` on history. - - -#### Easy Partial updates -Some features rely on updating some parts of the existing page. Imagine -implementing type-ahead search. In traditional applications, you may need a new -controller, routes, a discussion over versioning, JSON serializer, plenty of -new JS code, etc. - -![haircuts](https://thoughtbot.github.io/superglue/images/haircuts.png) - -With Superglue, this can be done with a simple `onChange` - -```js -const onChange = (e) => ( - remote(`/dashboard?qry=${e.target.value}&props_at=data.header.search`)} -) -``` - -?> `remote` and `visit` is a thunk [passed] to every page component. - - [passed]: ./navigation.md - -With `props_at`, the above will make a request to `/dashboard?qry=haircut`, -dig your template for the `data.header.search` node, return it in the response, -and immutably graft it in the exact same path on the redux store before finally -letting React re-render. - -For more on what you can do, check out our documentation. - -#### Server-Side Rendering -Server-Side Rendering is supported via [Humid](https://github.com/thoughtbot/humid). -See the [documentation for server-side rendering][ssr docs]. - - [ssr docs]: ./recipes/server-side-rendering.md - -## Documentation - -Documentation is hosted on [Github pages](https://thoughtbot.github.io/superglue). - -## Contributing - -Thank you, [contributors]! - - [contributors]: https://github.com/thoughtbot/superglue/graphs/contributors - -## Special Thanks - -Thanks to [jbuilder](https://github.com/rails/jbuilder), -[scour](https://github.com/rstacruz/scour), -[turbolinks3](https://github.com/turbolinks/turbolinks-classic), -[turbograft](https://github.com/Shopify/turbograft/), -[turbostreamer](https://github.com/malomalo/turbostreamer) diff --git a/docs/_sidebar.md b/docs/_sidebar.md deleted file mode 100644 index 713c579b..00000000 --- a/docs/_sidebar.md +++ /dev/null @@ -1,35 +0,0 @@ -- [Introduction](README.md) - -- Getting Started - - [Installation](installation.md) - - [Tutorial](tutorial.md) - -- [Demo](demo.md) - -- Concepts - - - [Philosophy](concepts.md) - - [The Redux state shape](redux-state-shape.md) - - [The page response](page-response.md) - -- Features - - - [Navigation](navigation.md) - - [Digging](traversal-guide.md) - - [Fragments and Slices](fragments-and-slices.md) - -- API - - [React Redux](react-redux.md) - - [Rails](rails.md) - - [Utility](utility.md) - -- Recipes - - [Modals](recipes/modals.md) - - [Server-Side Rendering](recipes/server-side-rendering.md) - - [Loading content later](recipes/load-content-later.md) - - [Loading a tab onClick](recipes/load-tab-onclick.md) - - [Updating shopping cart](recipes/update-shopping-cart.md) - - [Turbolinks navigation behavior](recipes/turbolinks.md) - - [Usage with Kaminari](recipes/kaminari.md) - - [Custom reducers](recipes/custom-reducers.md) - diff --git a/docs/concepts.md b/docs/concepts.md deleted file mode 100644 index 159efe31..00000000 --- a/docs/concepts.md +++ /dev/null @@ -1,96 +0,0 @@ -# Philosophy - -## Lean on Rails - -Move as much logic as you can into Rails. For example, instead of doing -this: - -```ruby - json.firstName @user.first_name -``` - -```js -const Header = ({firstName}) => (

{firstName.toUpperCase()}

) -``` - -we do - -```ruby - json.firstName @user.first_name.capitalize -``` - -```js -const Header = ({firstName}) => (

{firstName}

) -``` - -## Shape with presentation - -As an end user, its easy to look at any regular web page and understand all the -actions that you can take. Its the strength of HTML and of HATEOAS. Superglue is -inspired by this and encourages you to shape your JSON in the same way. - -Shape your pages roughly how you'll organize your components. Some may critic -the addition of presentation, but there is intentionally only one consumer of -your shape, your page components. - -?> **What about forms?** You can use [form_props] to build the right JSON for your -React components. - - [form_props]: https://github.com/thoughtbot/form_props - - -## HTML Thinking - -Just because you can create a link without a `href` in React doesn't mean you -should. Ultimately, everything renders as HTML. That's why Superglue doesn't -give links the ability to make non-get requests. Instead it encourages you to -create forms that look like links, the same approach Rails uses. This has the -added benefit of easily building for [Server Side Rendering]. - - [Server Side Rendering]: ./recipes/server-side-rendering.md - -## Embrace Unobtrusive Javascript (UJS) - -You may have noticed that we've been using `data-sg-remote` or `data-sg-visit` -in the examples. - -```jsx -
-``` - -Superglue embraces Unobtrusive Javascript. Any link or form with a `data-sg` -attribute receives superpowers inspired by Rails data attributes. - -For more advanced use cases, an action creator is [passed] to all your connected -page components. - - [passed]: ./navigation.md - - -## You can dig it! - -``` -/dashboard?props_at=data.greet -``` - -A single keypath will dig your template for content and place it to where it -belongs in your Redux state. That's incredibly productive and can be used for a -variety of rich applications. See the the [digging docs] or recipes section. - -## Its still just Rails - -We might using React components, but most of what gives Superglue superpowers is that -its driven by all the conveniences of old fashion and boring Rails. - -```treeview -app/ -|-- controllers/ -|-- views/ -| |-- posts/ -| | |-- index.js -| | |-- index.json.props -| | |-- index.html.erb -``` - -[props_template]: https://github.com/thoughtbot/props_template -[digging docs]: ./traversal-guide.md diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..697d4fe6 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,120 @@ +## `application_visit.js` + +!!! hint + If you want a progress bar, this is likely the first thing you'll + want to configure after installation. + +This file contains the factory that builds the [remote] and [visit] +function that will be passed to your page components and used by the +[data-sg-visit] and [data-sg-remote] UJS attributes. + +This file is meant for you to customize. For example, you'll likely +want to add a [progress bar], control how visits work, or flash +when the internet is down. + +[remote]: requests.md#remote +[visit]: requests.md#visit +[data-sg-remote]: ujs.md#data-sg-remote +[data-sg-visit]: ujs.md#data-sg-visit +[progress bar]: recipes/progress-bar.md + + +## `page_to_page_mapping.js` + +!!! hint + Stop by the [tutorial] to get an idea of how to work with this file. + +This file exports a mapping between a `componentIdentifier` to an imported page +component. This gets used in your `application.js` so that superglue knows +which component to render with which identifier. + +For example: + +```js +const pageIdentifierToPageComponent = { + 'posts/edit': PostsEdit, + 'posts/new': PostsNew, + 'posts/show': PostsShow, + 'posts/index': PostsIndex, +} +``` + +It's not uncommon to have multiple identifiers pointing to the same component. +This can be used when building `index` pages use modals instead of a new page for +`show`. + +```js +const pageIdentifierToPageComponent = { + 'posts/index': PostsIndex, + 'posts/show': PostsIndex, +} +``` + +[tutorial]: tutorial.md + +## `application.js` + +!!! hint + Normally you wouldn't need to configure this class as it'll be generated + for you. + +Your `Application` component inherits from Superglue's [ApplicationBase] +abstract class and is the entry point for your Superglue app. It overrides +the methods [buildStore], [visitAndRemote], and [mapping], to perform +setup of redux, UJS, and other functionality. + + +```js +import { ApplicationBase } from '@thoughtbot/superglue' + +export default class Application extends ApplicationBase { + ... +} +``` + + +
+ - [:octicons-arrow-right-24: See complete reference](reference/index.md#abstract-applicationbase) + for `ApplicationBase` +
+ +## `flash.js` + +The installation generator will add a `flash.js` slice to `app/javascript/slices` +and will work with the Rails `flash`. You can modify this however you like, out of the box: + + - When using `data-sg-visit`, all data in the flash slice will be cleared before the request. + - When using `data-sg-visit` or `data-sg-remote`, the recieved flash + will be merged with the current flash. You can change this behavior + by modifying the flash slice. + + +!!! hint + If you're curious how this works, in your layout, `application.json.props`, + the flash is serialized using `flash.to_h` + + +To use in your page components, simply use a selector. + +```jsx +import { useSelector } from 'react-redux' + +... + +const flash = useSelector((state) => state.flash) +``` + +then use the flash as you would normally in a controller + +```ruby +def create + flash[:success] = "Post was saved!" +end +``` + +[ApplicationBase]: reference/index.md#abstract-applicationbase +[buildStore]: reference/index.md#buildstore +[visitAndRemote]: requests.md +[mapping]: reference/index.md#mapping +[installation]: installation.md + diff --git a/docs/deferments.md b/docs/deferments.md new file mode 100644 index 00000000..cdb7df20 --- /dev/null +++ b/docs/deferments.md @@ -0,0 +1,101 @@ +Sometimes you may want to load parts of your page later, like a slow sidebar, a +graph that takes extra time to load, or tab content that shouldn't appear +immediately. These scenarios are perfect use cases for Deferments. + +Deferments are a low effort way to load content later, both automatically and +manually. Better yet, most of the work takes place in Rails land in your views. + +## `defer: :auto` + +This option make it easy to defer content in a single setting. + +=== "views/posts/index.json.props" + + ``` ruby + json.metrics(defer: [:auto, placeholder: {totalVisitors: 0}]) do + sleep 10 # expensive operation + json.totalVisitors 30 + end + ``` + +=== "views/layouts/application.json.props" + + ``` ruby + json.data do + yield + end + ``` + +And that's it! + +### Behind the scenes + +When a user lands on a page Superglue will receive + +```json +{ + data: { + metrics: { + totalVisitors: 0 + } + }, + defers:[ + {url: '/dashboard?props_at=data.metrics', type: "auto"} + ], + ...other +} +``` + +Your page components will receive `{metrics: {totalVisitors: 0}}` and render. Superglue will then +make a remote request: + +``` +remote("/dashboard?props_at=data.metrics") +``` + +10 seconds later the response succeeds with `{total_visitors: 30}`. Superglue +then immutably grafts that payload into the `/dashboard` page at the path +`data.metrics`. The page state would look like the following: + +``` +{ + data: { + metrics: { + totalVisitors: 30 + } + }, + defers:[...others], + ...other +} +``` + +Your page component finally recieves the new props and rerenders. For more +control, you may provide a `success_action` or `fail_action`, and Superglue +will dispatch these actions when the promise resolves successfully or fails. + +```ruby +json.metrics(defer: [:auto, placeholder: {totalVisitors: 0}, success_action: "SUCCESS", fail_action: "FAIL"]) do + sleep 10 # expensive operation + json.totalVisitors 30 +end +``` + +## `defer: :manual` + +When you want control over when deferred content loads, e.g., tabbed content, +use `defer: :manual` to stop the content from loading + +```ruby +json.metrics(defer: [:manual, placeholder: {totalVisitors: 0}]) do + sleep 10 # expensive operation + json.totalVisitors 30 +end +``` + +and manually use `remote` + +``` +remote("/dashboard?props_at=data.metrics") +``` + + diff --git a/docs/demo.md b/docs/demo.md index 50fc840f..2c0449fe 100644 --- a/docs/demo.md +++ b/docs/demo.md @@ -15,3 +15,4 @@ version] and comparing that with the [Superglue version]. [Sean Doyle]: https://github.com/seanpdoyle [Sean's version]: https://github.com/seanpdoyle/select-your-own-seat/commits/main [Superglue version]: https://github.com/thoughtbot/select-your-own-seat-superglue/commits/superglue + diff --git a/docs/traversal-guide.md b/docs/digging.md similarity index 64% rename from docs/traversal-guide.md rename to docs/digging.md index f2380896..3b950288 100644 --- a/docs/traversal-guide.md +++ b/docs/digging.md @@ -1,20 +1,21 @@ # Digging -Superglue's thunks work hand-in-hand with [PropsTemplate] to query your JSON -template for nodes. This guide helps you understand how the tools work with -each other. +Beyond full page navigation, Superglue can make selective updates to parts of +the page without a full load through digging. You may recognize digging from +earlier docs: -## The props_at param -The `props_at` param is a keypath to nodes in your tree and is used almost -exclusively with the `remote` thunk. On the PropsTemplate side, we pass that -param over to an internal node in order to walk your templates. +``` +/some_current_page?props_at=data.rightDrawer.dailySpecials +``` -?> `props_at` can be used with `data-sg-visit`, but only combined with -`data-sg-placeholder` to [build placeholders]. +By simply adding a `props_at` parameter to your requests, you can selectively +fetch parts of the page without incurring the cost of loading unneeded content. This +is great for functionality like modals, tabs, etc. -[build placeholders]: ./navigation.md#placeholders +## The `props_at` param -For example, with a template below. +The `props_at` param is a keypath to the content in your PropsTemplate. As a simplified +example, imagine this page with no layouts: ```ruby path = param_to_search_path(params[:props_at]) @@ -39,11 +40,23 @@ end ``` To fetch the `json.search` node, we would need to walk to `data` then `header` -then `search`. Translating that to a remote call with a `props_at` param: +then `search`. Translating that to a url with a `props_at` param: -```js -remote('/dashboard?props_at=data.header.search&some_search_str=haircuts') ``` +/dashboard?props_at=data.header.search&some_search_str=haircuts +``` + +Digging is normally combined with using [data-sg-remote] or [remote] to update +content in async fashion. + +!!! info + `props_at` can be used with `data-sg-visit`, but only combined with + [data-sg-placeholder]. + +[data-sg-placeholder]: ./ujs.md#data-sg-placeholder +[data-sg-remote]: ./ujs.md#data-sg-remote +[remote]: ./requests.md#remote + ## Collections There are two ways to query collections. Looking at the following example: @@ -134,13 +147,14 @@ json.array! @posts , key: :some_id do |post| end ``` -!> When querying, Superglue will disable -[caching](https://github.com/thoughtbot/props_template#caching) and -[deferment](https://github.com/thoughtbot/props_template#deferment) until the -target node is reached. +!!! info + When querying, Superglue will disable + [caching](https://github.com/thoughtbot/props_template#caching) and + [deferment](https://github.com/thoughtbot/props_template#deferment) until the + target node is reached. -That's the basics of traversing with Superglue. Many modern SPA functionality -can be achieved by just a few lines of code. For examples, see our recipes -section. +With digging, many modern SPA functionality can be achieved by just a keypath and a +few lines of code. [PropsTemplate]: https://github.com/thoughtbot/props_template + diff --git a/docs/fragments-and-slices.md b/docs/fragments-and-slices.md index 79767d1f..89491259 100644 --- a/docs/fragments-and-slices.md +++ b/docs/fragments-and-slices.md @@ -1,5 +1,8 @@ # Fragments and Slices +!!! warning + This functionality is experimental + When building pages, we commonly use partials to extract crosscutting concerns. For example, a shared header: @@ -43,7 +46,7 @@ across our `pages` slice: } ``` -Superglue fragments, and Redux slices are two recommended ways for updating cross-cutting +Superglue fragments, and Redux slices are two ways for updating cross-cutting concerns. # Fragments @@ -81,8 +84,9 @@ fragments: [ ] ``` -?> Fragments used in nodes that are [deferred](./navigation.md#deferments) do -not show up inside the metadata until the deferred nodes are loaded. +!!! info + Fragments used in nodes that are [deferred](./deferments.md) do + not show up inside the metadata until the deferred nodes are loaded. ## Automatic updates @@ -109,7 +113,7 @@ that can make this process easier. ### 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](./react-redux.md#applicationbase) +function in your [application.js](./configuration.md#applicationjs) ### extraReducers @@ -201,3 +205,4 @@ export const cartSlice = createSlice({ [page response]: ./page-response.md [slices]: ./page-response.md#slices [extraReducers]: https://redux-toolkit.js.org/api/createSlice#extrareducers + diff --git a/docs/functions-passed.md b/docs/functions-passed.md new file mode 100644 index 00000000..e8a5d102 --- /dev/null +++ b/docs/functions-passed.md @@ -0,0 +1,59 @@ +All page components receives the following functions. + +## visit and remote + +These are the methods described in [advanced requests]. + +[advanced requests]: ../requests.md + +## navigateTo + +Superglue comes with a basic `Nav` component that manages swapping of +the different page components. The component comes with a `navigateTo` method +that is passed to all your page components which you can use to perform a +full-page navigation using your cached state. + +```javascript +navigateTo('/posts', {ownProps:{something: true}}) +``` + +
+ - [:octicons-arrow-right-24: See complete reference](components.Nav.md#navigateto) + for `navigateTo` +
+ +## saveAndProcessPage + +Save and process a rendered view from PropsTemplate. This is the primitive +function that [visit and remote] calls when it receives a [page]. If you are +able to render a [page] outside the normal request response cycle, e.g, +websocket, you can use this function to save the payload. + +[page]: ../page-response.md + +| Arguments | Type | Notes | +| :--- | :--- | :--- | +| pageKey | `String` | The key that Superglue uses to store the response | +| page | `String` | A rendered PropsTemplate| + +### copyPage + +Copies an existing page in the store, and sets it to a different `pageKey`. +Useful for optimistic updates on the next page before you navigate. + +```js +copyPage({ + from: '/current_page', + to '/next_page' +}) +``` + +| Arguments | Type | Notes | +| :--- | :--- | :--- | +| {from} | `String` | The key of the page you want to copy from. +| {to} | `String` | The key of the page you want to copy to. + + +[digging guide]: ./digging.md +[PropsTemplate]: https://github.com/thoughtbot/props_template#partial-fragments +[visit and remote]: ../requests.md diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index c4e8da6c..00000000 --- a/docs/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - Document - - - - - - - - -
- - - - - - - - - - - - - - diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..76e93be0 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,195 @@ +# Overview + +Superglue is a library that thoughtfully pairs Rails and React. Its built with +The Rails Way in mind and aims to provide a simple developer experience that is +on par with Hotwire, Stimulus, and Turbo. Confidently use Rails routes, +controllers, views as you normally would in a multi-page application and +integrate with React's vast ecosystem. + + +!!! warning + Superglue is in active development as it approaches 1.0. While its + interface and behavior are stablizing, changes are expected. + +## Who is it for? + +Superglue is built from the ground up for Rails developers who + +- Have a complex React integration and want to move back to to a simple rails + workflow without giving up components already built. +- Want to use normal controllers, server side routing, views, helpers, etc. to + develop interactive React applications +- Want to hit the ground running with React and easily access all the components + that the React eco-system offers. +- Don't want to write another custom React integration +- Don't want to build APIs. +- Are tired of JS complexity and just want to get work done! + +## One-stop shop + +We know first hand how complex React can be, but we don't shy away from +complexity. We want to make things better for everyone and to that end, we +built a supporting cast of tooling under one shop to bring ease and consistancy +to your team. + +
+ +- __Superglue Rails__ + + --- + + Integrates Superglue with Rails, and generates a new + app. + + +- __PropsTemplate__ + + --- + + A very fast JSON builder. The secret sauce that enabled much more than just Rails/React integration + + [:octicons-arrow-right-24: props_template](#) + +- __Humid__ + + --- + + Server Side Rendering using MiniRacer and V8 isolates. + + [:octicons-arrow-right-24: Humid](#) + +- __Form Props__ + + --- + + Just use Rails forms with React. Combine it with React + components. + + [:octicons-arrow-right-24: form_props](#) + +
+ +## How does it work? + +### It’s Rails + +Superglue leans on Rails' ability to respond to different mime types on the +same route and divides the usual `foobar.html.erb` into three familiar +templates. + +- `foobar.json.props` A presenter written in a jbuilder-like template that + builds your page props. +- `foobar.js` Your page component that receives the props from above. +- `foobar.html.erb` Injects your page props into Redux when the browser loads + it. + +Shape your `props` to roughly how your components are presented. For example: + +```ruby +json.header do + json.username @user.username + json.linkToProfile url_for(@user) +end + +json.rightDrawer do + json.cart(partial: 'cart') do + end + json.dailySpecials(partial: 'specials') do + end +end + +json.body do + json.productFilter do + form_props(url: "/", method: "GET") do |f| + f.select(:category, ["lifestyle", "programming", "spiritual"]) + f.submit + end + end + + json.products do + json.array! @products do |product| + json.title product.title + json.urlToProduct url_for(product) + end + end +end + +json.footer do + json.copyrightYear "2023" +end +``` + +Familiar Rails conveniences include [form props], a fork of `form_with` made for +React; the [flash] is integrated as a [Redux slice]; and [Unobtrusive Javascript] (UJS) helpers. + +### It’s React + +But there are no APIs! The above is injected as a script tag in the DOM so everything +loads in the initial request. Its added to [your Redux state] and passed to +`foobar.js` as props, for example: + +```js +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Drawer, Header, Footer, ProductList, ProductFilter } from './components'; + +export default function FooBar({ header, products = [], productFilter, rightDrawer, footer }) { + const flash = useSelector((state) => state.flash); + + return ( + <> +

{flash && flash.notice}

+
+ +
+ + + + + +