Resource
Resource is a feature to manage async data fetching and mutations in your Vue frontend. It will fetch, cache and keep data up-to-date from the server.
Basic example
Any data that is fetched via a web request is called a resource in frappe-ui terminology. When you are dealing with async data, you are also dealing with loading states, error states, refetching etc. In the traditional way of fetching data, you have to handle loading states, error states, and refetching yourself.
let data, loading, error
try {
data = await fetch('https://jsonplaceholder.typicode.com/posts/1')
} catch (e) {
error = e
}
// rest of your code
The above example is still a very simplified version. You might also need a way to reload your data.
When you create a resource using the createResource
function, it will create a reactive object with properties like data
, loading
, error
, reload()
etc.
<template>
<Button @click="post.reload()" :loading="post.loading"> Reload </Button>
<pre>{{ post }}</pre>
</template>
<script setup>
import { createResource } from 'frappe-ui'
let post = createResource({
url: 'https://jsonplaceholder.typicode.com/posts/1',
})
post.fetch()
</script>
{ "url": "https://jsonplaceholder.typicode.com/posts/1", "data": null, "previousData": null, "loading": false, "fetched": false, "error": null, "promise": null, "params": null }
Options API example
Resources can also be used in options API style. You need to register the resourcesPlugin
first.
main.js
import { resourcesPlugin } from 'frappe-ui'
app.use(resourcesPlugin)
In your .vue
file, you can declare all your resources under the resources
key as functions. The actual resource object will be available on this.$resources.[name]
. In the following example, this.$resources.posts
is the resource object.
Component.vue
<template>
<pre>{{ $resources.posts }}</pre>
</template>
<script>
export default {
resources: {
posts() {
return {
url: 'https://jsonplaceholder.typicode.com/posts/1',
// option to call .fetch() the first time automatically
auto: true,
}
},
},
}
</script>
{ "url": "https://jsonplaceholder.typicode.com/posts/1", "data": null, "previousData": null, "loading": true, "fetched": false, "error": null, "promise": "[object Promise]", "auto": true, "params": null }
Caching example
Caching is a first-class feature in resources. To cache responses, just define a cache
property in options with a unique global key. Now, the response will cached in memory as well as in IndexedDB. If you define another resource in a different part of your application with the same cache key, it will reuse the cached one.
<template>
<Button @click="post.reload()" :loading="post.loading">
{{ post.fetched ? 'Reload' : 'Fetch data' }}
</Button>
<pre>{{ post.data }}</pre>
</template>
<script setup>
import { createResource } from 'frappe-ui'
let post = createResource({
url: 'https://jsonplaceholder.typicode.com/posts/1',
cache: 'posts',
})
</script>
List of Options and API
Here is the list of all options and APIs that are available on a resource.
Options
let post = createResource({
// partial rest api routes
url: '/api/posts/1'
// or full urls
url: 'https://jsonplaceholder.typicode.com/posts/1',
// http method: GET, POST, PUT, DELETE
method: 'GET',
// parameters
params: {
id: 1
},
// generate params from function
makeParams() {
return {
id: 1
}
},
// debounce request every 500ms
debounce: 500,
// initial data
initialData: []
// make the first request automatically
auto: true,
// cache key to cache the resource
// can be a string
cache: 'post',
// or an array that can be serialized
cache: ['post', '1'],
// you can also pass reactive variable here
cache: ['post', postId]
// events
// before making the request
beforeSubmit(params) {
},
// validate parameters before making request
validate(params) {
if (!params.id) {
// return a string message to throw an error
return 'id is required'
}
},
// error can occur from failed request and validate function
onError(error) {
},
// on successful response
onSuccess(data) {
},
// transform data before setting it
transform(data) {
for (let d of data) {
d.open = false
}
return data
},
})
API
let post = createResource({...})
post.data // data returned from request
post.loading // true when data is being fetched
post.error // error that occurred from making the request or from validate function
post.promise // promise object of the request, can be awaited
post.params // params that were sent for making the request, if using makeParams, the return value is set here
post.fetched // true when data has been fetched once, stays true after that
post.previousData // when you call .reload(), previousData is set to current data, and then data is set to new returned data
post.fetch() // make the web request (fetch call)
post.reload() // alias to fetch
post.submit() // alias to fetch
// you can also pass parameters while calling submit
post.submit({ id: 2 })
// reset the state of this resource as a newly created one
post.reset()
// update url and params
post.update({
url: '/api/users',
params: {
id: 2
}
})
// override data manually
post.setData({
id: 1,
title: 'test'
})
// modify existing data
post.setData(data => {
return data.filter(d => d.open)
})
Frappe Resource
Fetching data from a Frappe backend is no different from any other REST API service.
<template>
<Button @click="post.reload()" :loading="post.loading"> Reload </Button>
<pre>{{ post }}</pre>
</template>
<script setup>
import { createResource } from 'frappe-ui'
let todos = createResource({
url: '/api/method/frappe.client.get_list',
params: {
doctype: 'ToDo',
filters: {
allocated_to: 'faris@frappe.io',
},
},
})
todos.fetch()
</script>
But the response format by Frappe Framework requires some parsing to be done to extract data and errors. Since frappe-ui
is built primarily for Frappe backend apps, we can make it understand Frappe responses.
By default, resources use the request
function exported from frappe-ui
which is a generic Fetch API wrapper. There is another function frappeRequest
which is a wrapper for Frappe REST API calls. To make resources use it, you have to do the following:
main.js
import { setConfig, frappeRequest } from 'frappe-ui'
setConfig('resourceFetcher', frappeRequest)
Now, resources will use frappeRequest
for making the web requests. You can also drop the /api/method
part. The returned response will now set the data from message
key and error from exc
.
<template>
<Button @click="post.reload()" :loading="post.loading"> Reload </Button>
<pre>{{ post }}</pre>
</template>
<script setup>
import { createResource } from 'frappe-ui'
let todos = createResource({
url: '/api/method/frappe.client.get_list',
url: 'frappe.client.get_list',
params: {
doctype: 'ToDo',
filters: {
allocated_to: 'faris@frappe.io',
},
},
})
todos.fetch()
</script>