Skip to content

First class support for deferred and lazy loading #287

@sebastiandedeyne

Description

@sebastiandedeyne

Sometimes we need screens with a lot of data. Views that bring together a lot of concepts, and require a lot of queries and heavy lifting to load. We currently have the only option to make reloads more efficient, but this doesn't help the initial load.

Another place where this is useful is when you're loading data from an external API in real time, like a report from Stripe. That API call might take a few seconds to come through, so you'd want to send a response before the call is completed. With Inertia, this means you'd send a response without data, and do an API call afterwards.

public function index()
{
    return Inertia::render('Report');
}

public function transactions()
{
    return ['transactions' => Stripe::getTransactions()];
}
<script>
export default {
  props: [],
  data: () => ({ transactions: null }),
  created() {
    fetch('transactions')
      .then(response => response.json())
      .then(data => {
        this.transactions = data.transactions
      })
  }
}
</script>

This works, but with Inertia we should be able to do better. With some creative coding and partial reloads, we can already get this done without the API endpoint.

public function index()
{
    return Inertia::render('Report', [
        'transactions' => in_array('transactions', explode(',', Request::header('X-Inertia-Partial-Data'))))
            ? Stripe::getTransactions()
            : null,
        ]);
}
<script>
export default {
  props: ['transactions'],
  created() {
    this.$inertia.reload({ only: ['transactions'] })
  }
}
</script>

This returns null for transactions on the first visit, and calls Stripe::getTransactions() when partially reloading. The first visit will be fast, when explicitly requesting transactions it'll be slow.

Improvements here are:

  • No more additional API endpoint
  • No more internal state, all props

This could become a first class citizen. In that case, the controller would look cleaner, and Inertia would take care of the second request.

public function index()
{
    return Inertia::render('Report', [
        'transactions' => Inertia::defer(fn => Stripe::getTransactions()),
    ]);
}
<script>
export default {
  props: ['transactions']
}
</script>

I think defer or lazy communicates the feature pretty well. I prefer defer here, so we can use lazy for the next step.

Additional feature: lazy loading

The defer feature could be extended to give the user control when the second load occurs. This could be useful to pair with IntersectionObserver, so you would only load additional data when the visitor is about to see it.

The hardest part about this is setting up a proper API.

public function index()
{
    return Inertia::render('Report', [
        'transactions' => Inertia::lazy(fn => Stripe::getTransactions()),
    ]);
}
<script>
export default {
  props: ['transactions'],
  created() {
    new IntersectionObserver(() => {
      this.$inertia.reload({ only: ['transactions' ]})
    }, { … })
  }
}
</script>

If this was a thing, we could add a more fitting method to Inertia, like this.$inertia.load('transactions') to hide the implementation details of partial reloading.

Additional feature: parallel versus serial updates

This one could bring a lot of problems, and is probably over the top. But in some cases it might be more useful to load the additional data in parallel.

public function index()
{
    return Inertia::render('Dashboard', [
        'metricA' => Inertia::deferred(fn => …),
        'metricB' => Inertia::deferred(fn => …),
        'metricC' => Inertia::deferred(fn => …),
        'metricD' => Inertia::deferred(fn => …),
        'metricE' => Inertia::deferred(fn => …),
    ]);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreRelated to the core Inertia libraryinvestigateThe issue needs further investigating

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions