Skip to content

Commit

Permalink
Static props are now passed with wrap
Browse files Browse the repository at this point in the history
  • Loading branch information
ItalyPaleAle committed Sep 20, 2020
1 parent c556e72 commit 4a0b5e6
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 80 deletions.
78 changes: 41 additions & 37 deletions Advanced Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ Thanks to the many features of Svelte 3 or other components in the ecosystem, sv
- [Dynamically-imported routes and placeholders](#async-routes-and-loading-placeholders)
- [Route pre-conditions](#route-pre-conditions) ("route guards")
- [Adding user data to routes](#user-data)
- [Static props](#static-props)
- [`routeEvent` event](#routeevent-event)
- [`routeLoading` and `routeLoaded` events](#routeloading-and-routeloaded-events)
- [Querystring parsing](#querystring-parsing)
- [Static props](#static-props)
- [Route transitions](#route-transitions)
- [Nested routers](#nested-routers)
- [Route groups](#route-groups)
Expand All @@ -26,6 +26,7 @@ The `wrap` method allows a few more interesting features, however:
- In addition to dynamically-importing components, you can define a component to be shown while a dynamically-imported one is being requested
- You can add pre-conditions to routes (sometimes called "route guards")
- You can add custom user data that is then used with the [`routeLoading` and `routeLoaded` events](#routeloading-and-routeloaded-events)
- You can set static props, which are passed to the component as mounted by the router

### The `wrap` method

Expand All @@ -42,7 +43,8 @@ It accepts a single `options` argument that is an object with the following prop
- `options.loadingComponent`: Used together with `asyncComponent`, this is a Svelte component, that must be part of the bundle, which is displayed while `asyncComponent` is being downloaded. If this is empty, then the router will not display any component while the request is in progress.
- `options.loadingParams`: When using a `loadingComponent`, this is an optional dictionary that will be passed to the component as the `params` prop.
- `options.userData`: Optional dictionary that will be passed to events such as `routeLoading`, `routeLoaded`, `conditionsFailed`.
- `options.conditions`: Array of route pre-condition functions to add, which will be executed in order.
- `options.conditions`: Optional array of route pre-condition functions to add, which will be executed in order.
- `options.props`: Optional dictionary of props that are passed to the component when mounted. The props are expanded with the spread operator (`{...props}`), so the key of each element becomes the name of the prop.

One and only one of `options.component` or `options.asyncComponent` must be set; all other properties are optional.

Expand Down Expand Up @@ -269,6 +271,43 @@ function routeLoaded(event) {
</script>
````

### Static props

In certain cases, you might need to pass static props to a component within the router.

For example, assume this component `Foo.svelte`:

```svelte
<p>The secret number is {num}</p>
<script>
// Prop
export let num
</script>
```

If `Foo` is a route in your application, you can pass a series of props to it through the router, using `wrap`:

```svelte
<Router {routes} {props} />
<script>
// Import the router and routes
import Router from 'svelte-spa-router'
import {wrap} from 'svelte-spa-router/wrap'
import Foo from './Foo.svelte'
// Route definition object
const routes = {
'/': wrap({
component: Foo,
// Static props
props: {
num: 42
}
})
}
</script>
```

## `routeEvent` event

The custom `routeEvent` event can be used to bubble events from a component displayed by the router, to the router's parent component.
Expand Down Expand Up @@ -427,41 +466,6 @@ With the same URL as before, the result would be:

qs supports advanced things such as arrays, nested objects, etc. Check out their [README](https://github.com/ljharb/qs) for more information.

## Static props

In certain cases, you might need to pass static props to a component within the router.

For example, assume this component `Foo.svelte`:

```svelte
<p>The secret number is {num}</p>
<script>
// Prop
export let num
</script>
```

If `Foo` is a route in your application, you can pass a series of props to it through the router, with:

```svelte
<Router {routes} {props} />
<script>
// Import the router and routes
import Router from 'svelte-spa-router'
import Foo from './Foo.svelte'
// Route definition object
const routes = {
'/': Foo
}
// Static props
const props = {
num: 42
}
</script>
```

## Route transitions

It's easy to add a nice transition between routes, leveraging the built-in [transitions](https://svelte.dev/docs#Transitions) of Svelte 3.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ const routes = {
}
````

The `wrap` method accepts an object with multiple properties and enables other features, including: setting a "loading" component that is shown while a dynamically-imported component is being requested, adding custom user data, adding pre-conditions (route guards), etc.
The `wrap` method accepts an object with multiple properties and enables other features, including: setting a "loading" component that is shown while a dynamically-imported component is being requested, adding pre-conditions (route guards), passing static props, and adding custom user data.

You can learn more about all the features of `wrap` in the documentation for [route wrapping](/Advanced%20Usage.md#route-wrapping).

Expand Down Expand Up @@ -369,6 +369,7 @@ Check out the [Advanced Usage](/Advanced%20Usage.md) documentation for using:
- [Dynamically-imported routes and placeholders](/Advanced%20Usage.md#async-routes-and-loading-placeholders)
- [Route pre-conditions](/Advanced%20Usage.md#route-pre-conditions) ("route guards")
- [Adding user data to routes](/Advanced%20Usage.md#user-data)
- [Static props](/Advanced%20Usage.md#static-props)
- [`routeEvent` event](/Advanced%20Usage.md#routeevent-event)
- [`routeLoading` and `routeLoaded` events](/Advanced%20Usage.md#routeloading-and-routeloaded-events)
- [Querystring parsing](/Advanced%20Usage.md#querystring-parsing)
Expand Down
14 changes: 8 additions & 6 deletions Router.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,6 @@ export let prefix = ''
*/
export let restoreScrollState = false
/**
* Optional props that are passed to each component in the router, expanded (with `{...props}`)
*/
export let props = {}
/**
* Container for a route: path, component
*/
Expand All @@ -259,7 +254,7 @@ class RouteItem {
* Initializes the object and creates a regular expression from the path, using regexparam.
*
* @param {string} path - Path to the route (must start with '/' or '*')
* @param {SvelteComponent} component - Svelte component for the route
* @param {SvelteComponent|WrappedComponent} component - Svelte component for the route, optionally wrapped
*/
constructor(path, component) {
if (!component || (typeof component != 'function' && (typeof component != 'object' || component._sveltesparouter !== true))) {
Expand All @@ -283,11 +278,13 @@ class RouteItem {
this.component = component.component
this.conditions = component.conditions || []
this.userData = component.userData
this.props = component.props || {}
}
else {
// Convert the component to a function that returns a Promise, to normalize it
this.component = () => Promise.resolve(component)
this.conditions = []
this.props = {}
}
this._pattern = pattern
Expand Down Expand Up @@ -388,6 +385,7 @@ else {
// Props for the component to render
let component = null
let componentParams = null
let props = {}
// Event dispatcher from Svelte
const dispatch = createEventDispatcher()
Expand Down Expand Up @@ -467,6 +465,7 @@ loc.subscribe(async (newLoc) => {
if (obj.loading) {
component = obj.loading
componentParams = obj.loadingParams
props = {}
// Trigger the routeLoaded event for the loading component
// Create a copy of detail so we don't modify the object for the dynamic route (and the dynamic route doesn't modify our object too)
Expand Down Expand Up @@ -504,6 +503,9 @@ loc.subscribe(async (newLoc) => {
componentParams = null
}
// Set static props, if any
props = routesList[i].props
dispatchNextTick('routeLoaded', detail)
break
}
Expand Down
32 changes: 8 additions & 24 deletions test/app/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,14 @@
</p>

<!-- Show the router -->
{#if staticProps}
<Router
{routes}
on:conditionsFailed={conditionsFailed}
on:routeLoaded={routeLoaded}
on:routeLoading={routeLoading}
on:routeEvent={routeEvent}
{restoreScrollState}
props={staticProps}
/>
{:else}
<Router
{routes}
on:conditionsFailed={conditionsFailed}
on:routeLoaded={routeLoaded}
on:routeLoading={routeLoading}
on:routeEvent={routeEvent}
{restoreScrollState}
/>
{/if}
<Router
{routes}
on:conditionsFailed={conditionsFailed}
on:routeLoaded={routeLoaded}
on:routeLoading={routeLoading}
on:routeEvent={routeEvent}
{restoreScrollState}
/>

<!-- Testing dynamic list of links -->
<h2>Dynamic links</h2>
Expand Down Expand Up @@ -127,10 +115,6 @@ function routeEvent(event) {
const urlParams = new URLSearchParams(window.location.search)
const restoreScrollState = !!urlParams.has('scroll')
// Check if we need to set static props, which are enabled by setting the "props=1" querystring parameter
// We're checking this for the tests, but in your code you will likely want to set this value manually
const staticProps = urlParams.has('props') ? {staticProp: 'foo'} : null
// List of dynamic links
let dynamicLinks = [
{
Expand Down
13 changes: 13 additions & 0 deletions test/app/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Home from './routes/Home.svelte'
import Name from './routes/Name.svelte'
import Loading from './routes/Loading.svelte'
import Wild from './routes/Wild.svelte'
import Foo from './routes/Foo.svelte'
import Regex from './routes/Regex.svelte'
import NotFound from './routes/NotFound.svelte'

Expand Down Expand Up @@ -96,6 +97,12 @@ if (!urlParams.has('routemap')) {
// The second argument is a custom data object that will be passed to the `conditionsFailed` event if the pre-conditions fail
'/lucky': wrappedLuckyRoute,

// This route has a static prop that is passed to it
'/foo': wrap({
component: Foo,
props: {staticProp: 'this is static'}
}),

// This component contains a nested router
// Note that we must match both '/nested' and '/nested/*' for the nested router to work (or look below at doing this with a Map and a regular expression)
'/nested': wrap({
Expand Down Expand Up @@ -134,6 +141,12 @@ else {
// The second argument is a custom data object that will be passed to the `conditionsFailed` event if the pre-conditions fail
routes.set('/lucky', wrappedLuckyRoute)

// This route has a static prop that is passed to it
routes.set('/foo', wrap({
component: Foo,
props: {staticProp: 'this is static'}
}))

// Regular expressions
routes.set(/^\/regex\/(.*)?/i, Regex)
routes.set(/^\/(pattern|match)(\/[a-z0-9]+)?/i, Regex)
Expand Down
10 changes: 10 additions & 0 deletions test/app/src/routes/Foo.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{#if staticProp}
<p>We have a static prop: <b id="staticprop">{staticProp}</b></p>
{:else}
<p>No static props here!</p>
{/if}

<script>
// Static props if they're set
export let staticProp = null
</script>
9 changes: 0 additions & 9 deletions test/app/src/routes/Home.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,3 @@
<li>Manually change the URL's fragment/hash</li>
<li>Try refreshing the page</li>
</ul>

{#if staticProp}
<p>We have a static prop: <b id="staticprop">{staticProp}</b></p>
{/if}

<script>
// Static props if they're set
export let staticProp = null
</script>
4 changes: 2 additions & 2 deletions test/cases/01-routing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,9 @@ describe('<Router> component', function() {

it('static props', (browser) => {
browser
.url(browser.launchUrl + '?props=1')
.url(browser.launchUrl + '/#/foo')
.waitForElementVisible('#staticprop')
.expect.element('#staticprop').text.to.equal('foo')
.expect.element('#staticprop').text.to.equal('this is static')

browser.end()
})
Expand Down
10 changes: 9 additions & 1 deletion wrap.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/**
* @typedef {Object} WrappedComponent
* @property {SvelteComponent} component - Component to load (this is always asynchronous)
* @property {RoutePrecondition[]} [conditions] - Route pre-conditions to validate
* @property {Object} [props] - Optional dictionary of static props
* @property {Object} [userData] - Optional user data dictionary
* @property {bool} _sveltesparouter - Internal flag; always set to true
*/

/**
Expand All @@ -15,14 +20,16 @@
* @property {SvelteComponent} [loadingComponent] - Svelte component to be displayed while the async route is loading (as a placeholder); when unset or false-y, no component is shown while component
* @property {Object} [loadingParams] - Optional dictionary passed to the `loadingComponent` component as params (for an exported prop called `params`)
* @property {Object} [userData] - Optional object that will be passed to events such as `routeLoading`, `routeLoaded`, `conditionsFailed`
* @property {Object} [props] - Optional key-value dictionary of static props that will be passed to the component. The props are expanded with {...props}, so the key in the dictionary becomes the name of the prop.
* @property {RoutePrecondition[]|RoutePrecondition} [conditions] - Route pre-conditions to add, which will be executed in order
*/

/**
* Wraps a component to enable multiple capabilities:
* 1. Using dynamically-imported component, with (e.g. `{asyncComponent: () => import('Foo.svelte')}`), which also allows bundlers to do code-splitting.
* 2. Adding route pre-conditions (e.g. `{conditions: [...]}`)
* 3. Adding custom userData, which is passed to route events (e.g. route loaded events) or to route pre-conditions (e.g. `{userData: {foo: 'bar}}`)
* 3. Adding static props that are passed to the component
* 4. Adding custom userData, which is passed to route events (e.g. route loaded events) or to route pre-conditions (e.g. `{userData: {foo: 'bar}}`)
*
* @param {WrapOptions} args - Arguments object
* @returns {WrappedComponent} Wrapped component
Expand Down Expand Up @@ -71,6 +78,7 @@ export function wrap(args) {
component: args.asyncComponent,
userData: args.userData,
conditions: (args.conditions && args.conditions.length) ? args.conditions : undefined,
props: (args.props && Object.keys(args.props).length) ? args.props : {},
_sveltesparouter: true
}

Expand Down

0 comments on commit 4a0b5e6

Please sign in to comment.