Skip to content

Commit

Permalink
deferred props
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Sarvarov committed Oct 11, 2024
1 parent df27c8a commit 364bcc4
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 76 deletions.
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,30 @@ i, err := inertia.New(

Also, you have to use asset bundling tools like [Vite](https://vitejs.dev/) or [Webpack](https://webpack.js.org/) (especially with [Laravel Mix](https://laravel-mix.com/)). The setup will vary depending on this choice, you can read more about it in [official docs](https://inertiajs.com/server-side-rendering) or check an [example](https://github.com/hbourgeot/gonertia_vue_example) that works on Vite.

#### Lazy and Always props ([learn more](https://inertiajs.com/partial-reloads))
#### Optional and Always props ([learn more](https://inertiajs.com/partial-reloads))

```go
props := inertia.Props{
"lazy": inertia.LazyProp{func () (any, error) {
"optional": inertia.Optional{func () (any, error) {
return "prop", nil
}},
"always": inertia.AlwaysProp{"prop"},
"always": inertia.Always("prop"),
}

i.Render(w, r, "Some/Page", props)
```

#### Deferred props ([learn more](https://v2.inertiajs.com/deferred-props))

```go
props := inertia.Props{
"defer_with_default_group": inertia.Defer(func () (any, error) {
return "prop", nil
}),
"defer_with_custom_group": inertia.Defer("prop", "foobar"),
}
```

#### Redirects ([learn more](https://inertiajs.com/redirects))

```go
Expand Down Expand Up @@ -301,6 +312,29 @@ func (p *InmemFlashProvider) GetErrors(ctx context.Context) (ValidationErrors, e
}
```

#### History encryption ([learn more](https://v2.inertiajs.com/history-encryption))

Encrypt history:
```go
// Global encryption:
i, err := inertia.New(
/* ... */
inertia.WithEncryptHistory(),
)

// Pre-request encryption:
ctx := inertia.SetEncryptHistory(r.Context())

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
```

Clear history:
```go
ctx := inertia.SetClearHistory(r.Context())

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
```

#### Testing

Of course, this package provides convenient interfaces for testing!
Expand All @@ -324,6 +358,7 @@ func TestHomepage(t *testing.T) {
assertable.AssertProps(inertia.Props{"foo": "bar"})
assertable.AssertEncryptHistory(true)
assertable.AssertClearHistory(true)
assertable.AssertDeferredProps(map[string][]string{"default": []string{"foo bar"}})

// or work with the data yourself:
assertable.Component // Foo/Bar
Expand Down
138 changes: 102 additions & 36 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,59 @@ type TemplateFuncs map[string]any
// and will be available in the front-end component.
type Props map[string]any

// LazyProp is a property value that will only evaluated then needed.
// OptionalProp is a property that will evaluate then needed.
//
// https://inertiajs.com/partial-reloads
type LazyProp struct {
type OptionalProp struct {
Value any
}

func (p LazyProp) Prop() any {
func (p OptionalProp) Prop() any {
return p.Value
}

// AlwaysProp is a property value that will always evaluated.
func (p OptionalProp) shouldIgnoreFirstLoad() bool {
return true
}

func Optional(value any) OptionalProp {
return OptionalProp{Value: value}
}

type ignoreFirstLoad interface {
shouldIgnoreFirstLoad() bool
}

var _ ignoreFirstLoad = OptionalProp{}

// Deprecated: use OptionalProp.
type LazyProp = OptionalProp

// Deprecated: use Optional.
func Lazy(value any) LazyProp {
return LazyProp{Value: value}
}

// DeferProp is a property that will evaluate after page load.
//
// https://v2.inertiajs.com/deferred-props
type DeferProp struct {
OptionalProp
Group string
}

func (p DeferProp) Prop() any {
return p.Value
}

func Defer(value any, group ...string) DeferProp {
return DeferProp{
OptionalProp: Optional(value),
Group: firstOr[string](group, "default"),
}
}

// AlwaysProp is a property that will always evaluated.
//
// https://github.com/inertiajs/inertia-laravel/pull/627
type AlwaysProp struct {
Expand All @@ -41,6 +82,10 @@ func (p AlwaysProp) Prop() any {
return p.Value
}

func Always(value any) AlwaysProp {
return AlwaysProp{Value: value}
}

// Proper is an interface for custom type, which provides property, that will be resolved.
type Proper interface {
Prop() any
Expand Down Expand Up @@ -132,16 +177,19 @@ func (i *Inertia) Render(w http.ResponseWriter, r *http.Request, component strin
}

type page struct {
Component string `json:"component"`
Props Props `json:"props"`
URL string `json:"url"`
Version string `json:"version"`
EncryptHistory bool `json:"encryptHistory"`
ClearHistory bool `json:"clearHistory"`
Component string `json:"component"`
Props Props `json:"props"`
URL string `json:"url"`
Version string `json:"version"`
EncryptHistory bool `json:"encryptHistory"`
ClearHistory bool `json:"clearHistory"`
DeferredProps map[string][]string `json:"deferredProps,omitempty"`
}

func (i *Inertia) buildPage(r *http.Request, component string, props Props) (*page, error) {
props, err := i.prepareProps(r, component, props)
deferredProps := resolveDeferredProps(r, component, props)

props, err := i.resolveProperties(r, component, props)
if err != nil {
return nil, fmt.Errorf("prepare props: %w", err)
}
Expand All @@ -153,10 +201,11 @@ func (i *Inertia) buildPage(r *http.Request, component string, props Props) (*pa
Version: i.version,
EncryptHistory: i.resolveEncryptHistory(r.Context()),
ClearHistory: ClearHistoryFromContext(r.Context()),
DeferredProps: deferredProps,
}, nil
}

func (i *Inertia) prepareProps(r *http.Request, component string, props Props) (Props, error) {
func (i *Inertia) resolveProperties(r *http.Request, component string, props Props) (Props, error) {
result := make(Props)

{
Expand All @@ -182,54 +231,55 @@ func (i *Inertia) prepareProps(r *http.Request, component string, props Props) (
}

{
// Only (include keys) and except (exclude keys) logic.
only, except := i.getOnlyAndExcept(r, component)
// Partial reloads only work for visits made to the same page component.
//
// https://inertiajs.com/partial-reloads
if isPartial(r, component) {

Check failure on line 237 in response.go

View workflow job for this annotation

GitHub Actions / audit

`if isPartial(r, component)` has complex nested blocks (complexity: 7) (nestif)

Check failure on line 237 in response.go

View workflow job for this annotation

GitHub Actions / audit

`if isPartial(r, component)` has complex nested blocks (complexity: 7) (nestif)
// Only (include keys) and except (exclude keys) logic.
only, except := getOnlyAndExcept(r)

if len(only) > 0 {
for key, val := range result {
if _, ok := only[key]; ok {
continue
}
if _, ok := val.(AlwaysProp); ok {
continue
}

// Filter props.
if len(only) > 0 {
for key, val := range result {
if _, ok := only[key]; ok {
continue
}
if _, ok := val.(AlwaysProp); ok {
continue
delete(result, key)
}

}
for key := range except {
delete(result, key)
}
} else {
// Props with ignoreFirstLoad should not be included.
for key, val := range result {
if _, ok := val.(LazyProp); ok {
if ifl, ok := val.(ignoreFirstLoad); ok && ifl.shouldIgnoreFirstLoad() {
delete(result, key)
}
}
}
for key := range except {
delete(result, key)
}
}

// Resolve props values.
for key, val := range result {
var err error
val, err = resolvePropVal(val)
result[key], err = resolvePropVal(val)
if err != nil {
return nil, fmt.Errorf("resolve prop value: %w", err)
}
result[key] = val
}

return result, nil
}

func (i *Inertia) getOnlyAndExcept(r *http.Request, component string) (only, except map[string]struct{}) {
// Partial reloads only work for visits made to the same page component.
//
// https://inertiajs.com/partial-reloads
if partialComponentFromRequest(r) != component {
return nil, nil
}
func isPartial(r *http.Request, component string) bool {
return partialComponentFromRequest(r) == component
}

func getOnlyAndExcept(r *http.Request) (only, except map[string]struct{}) {
return setOf[string](onlyFromRequest(r)), setOf[string](exceptFromRequest(r))
}

Expand Down Expand Up @@ -257,6 +307,22 @@ func resolvePropVal(val any) (_ any, err error) {
return val, nil
}

func resolveDeferredProps(r *http.Request, component string, props Props) map[string][]string {
if isPartial(r, component) {
return nil
}

keysByGroups := make(map[string][]string)

for key, val := range props {
if dp, ok := val.(DeferProp); ok {
keysByGroups[dp.Group] = append(keysByGroups[dp.Group], key)
}
}

return keysByGroups
}

func (i *Inertia) resolveEncryptHistory(ctx context.Context) bool {
encryptHistory, ok := EncryptHistoryFromContext(ctx)
if ok {
Expand Down
Loading

0 comments on commit 364bcc4

Please sign in to comment.