sunmao-ui
sunmao-ui copied to clipboard
Proposal: Define a method to be an async function
The Problem
Currently, if we want to call the method after the fetch trait is complete or error, we need to add all the handlers into the fetch trait’s onComplete
and use the disabled
to control whether they should be executed. In addition, we also need to add the global states to mark the different trigger ways and clear their values after using it which brings a lot of mental burdens.
For example, I want to implement this demand:
- when click
button1
: callapi
→ setstate
value →setinput1
's value tobutton1
after complete - when click
button2
: callapi
→ setstate
value → setinput1
's value tobutton2
after complete
The whole application schema to be this:
{
"version": "sunmao/v1",
"kind": "Application",
"metadata": {
"name": "some App"
},
"spec": {
"components": [
{
"id": "api",
"type": "core/v1/dummy",
"properties": {},
"traits": [
{
"type": "core/v1/fetch",
"properties": {
"url": "",
"method": "get",
"lazy": true,
"disabled": false,
"headers": {},
"body": "{{[]}}",
"bodyType": "json",
"onComplete": [
{
"componentId": "state",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{api.fetch.data}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
},
{
"componentId": "input1",
"method": {
"name": "setInputValue",
"parameters": {
"value": "button1"
}
},
"disabled": "{{!isClickButton1.value}}",
"wait": {
"type": "delay",
"time": 0
}
},
{
"componentId": "input1",
"method": {
"name": "setInputValue",
"parameters": {
"value": "button2"
}
},
"disabled": "{{!isClickButton2.value}}",
"wait": {
"type": "delay",
"time": 0
}
},
{
"componentId": "isClickButton1",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{false}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
},
{
"componentId": "isClickButton2",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{false}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
}
],
"onError": [
{
"componentId": "isClickButton1",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{false}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
},
{
"componentId": "isClickButton2",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{false}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
}
]
}
}
]
},
{
"id": "isClickButton1",
"type": "core/v1/dummy",
"properties": {},
"traits": [
{
"type": "core/v1/state",
"properties": {
"key": "value",
"initialValue": "{{false}}"
}
}
]
},
{
"id": "isClickButton2",
"type": "core/v1/dummy",
"properties": {},
"traits": [
{
"type": "core/v1/state",
"properties": {
"key": "value",
"initialValue": "{{false}}"
}
}
]
},
{
"id": "input1",
"type": "chakra_ui/v1/input",
"properties": {
"variant": "outline",
"placeholder": "Please input value",
"size": "md",
"focusBorderColor": "",
"isDisabled": false,
"isRequired": false,
"defaultValue": ""
},
"traits": []
},
{
"id": "button1",
"type": "arco/v1/button",
"properties": {
"type": "default",
"status": "default",
"long": false,
"size": "default",
"disabled": false,
"loading": false,
"shape": "square",
"text": "button1"
},
"traits": [
{
"type": "core/v1/event",
"properties": {
"handlers": [
{
"type": "onClick",
"componentId": "isClickButton1",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{true}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
},
{
"type": "onClick",
"componentId": "api",
"method": {
"name": "triggerFetch",
"parameters": {}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
}
]
}
}
]
},
{
"id": "button2",
"type": "arco/v1/button",
"properties": {
"type": "default",
"status": "default",
"long": false,
"size": "default",
"disabled": false,
"loading": false,
"shape": "square",
"text": "button2"
},
"traits": [
{
"type": "core/v1/event",
"properties": {
"handlers": [
{
"type": "onClick",
"componentId": "isClickButton2",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{true}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
},
{
"type": "onClick",
"componentId": "api",
"method": {
"name": "triggerFetch",
"parameters": {}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
}
]
}
}
]
},
{
"id": "state",
"type": "core/v1/dummy",
"properties": {},
"traits": [
{
"type": "core/v1/state",
"properties": {
"key": "value"
}
}
]
}
]
}
}
The logic turn to JavaScript code just like this:
function trigger () {
api().then(()=> {
setState()
if (isClickButton1) {
setInput1('button1')
}
if (isClickButton2) {
setInput1('button2')
}
setIsClickButton1(false)
setIsClickButton2(false)
}).catch(()=> {
setIsClickButton1(false)
setIsClickButton2(false)
})
}
function handleClickButton1 () {
setIsClickButton1(true)
trigger()
}
function handleClickButton2 () {
setIsClickButton2(true)
trigger()
}
🙃 There are so awful to do this.
Improvement
To improve this, I want to define whether the method is the async function.
The spec to define a method to be the async function may be like this:
{
methods: [
{
name: 'triggerFetch',
parameters: Type.Object({}),
async: true
},
],
}
Then if the method is an async function, it would support the onComplete
and onError
in the schema to call other methods.
// packages/runtime/src/components/_internal/ImplWrapper/hooks/useGlobalHandlerMap.ts
const handler = async (schema) => {
const method = handlerMap[schema.name];
if (method.async) {
try {
await method(schema.parameters);
c.onComplete.forEach(onCompleteschema=> generateCallback(onCompleteschema)());
} catch {
c.onError.forEach(onErrorSchema=> generateCallback(onErrorSchema)());
}
} else {
method(s.parameters);
}
};
// packages/runtime/src/traits/core/Fetch.tsx
export default implementRuntimeTrait({
version: CORE_VERSION,
metadata: {
name: CoreTraitName.Fetch,
description: 'fetch data to store',
},
...
})(()=> {
...
const fetchData = () => {
...
return fetch();
};
subscribeMethods({
triggerFetch() {
return fetchData();
},
...
});
...
})
The whole schema may be like this:
{
"version": "sunmao/v1",
"kind": "Application",
"metadata": {
"name": "some App"
},
"spec": {
"components": [
{
"id": "api",
"type": "core/v1/dummy",
"properties": {},
"traits": [
{
"type": "core/v1/fetch",
"properties": {
"url": "",
"method": "get",
"lazy": true,
"disabled": false,
"headers": {},
"body": "{{[]}}",
"bodyType": "json",
"onComplete": [
{
"componentId": "state",
"method": {
"name": "setValue",
"parameters": {
"key": "value",
"value": "{{api.fetch.data}}"
}
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
}
],
"onError": []
}
}
]
},
{
"id": "input1",
"type": "chakra_ui/v1/input",
"properties": {
"variant": "outline",
"placeholder": "Please input value",
"size": "md",
"focusBorderColor": "",
"isDisabled": false,
"isRequired": false,
"defaultValue": ""
},
"traits": []
},
{
"id": "button1",
"type": "arco/v1/button",
"properties": {
"type": "default",
"status": "default",
"long": false,
"size": "default",
"disabled": false,
"loading": false,
"shape": "square",
"text": "button1"
},
"traits": [
{
"type": "core/v1/event",
"properties": {
"handlers": [
{
"type": "onClick",
"componentId": "api",
"method": {
"name": "triggerFetch",
"parameters": {},
+ "onComplete": [
+ {
+ "componentId": "input1",
+ "method": {
+ "name": "setInputValue",
+ "parameters": {
+ "key": "value",
+ "value": "button1"
+ }
+ },
+ "disabled": false,
+ "wait": {
+ "type": "delay",
+ "time": 0
+ }
+ }
+ ]
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
}
]
}
}
]
},
{
"id": "button2",
"type": "arco/v1/button",
"properties": {
"type": "default",
"status": "default",
"long": false,
"size": "default",
"disabled": false,
"loading": false,
"shape": "square",
"text": "button2"
},
"traits": [
{
"type": "core/v1/event",
"properties": {
"handlers": [
{
"type": "onClick",
"componentId": "api",
"method": {
"name": "triggerFetch",
"parameters": {},
+ "onComplete": [
+ {
+ "componentId": "input1",
+ "method": {
+ "name": "setInputValue",
+ "parameters": {
+ "key": "value",
+ "value": "button2"
+ }
+ },
+ "disabled": false,
+ "wait": {
+ "type": "delay",
+ "time": 0
+ }
+ }
+ ]
},
"disabled": false,
"wait": {
"type": "delay",
"time": 0
}
}
]
}
}
]
},
{
"id": "state",
"type": "core/v1/dummy",
"properties": {},
"traits": [
{
"type": "core/v1/state",
"properties": {
"key": "value"
}
}
]
}
]
}
}
The logic turn to JavaScript code just like this:
async function trigger () {
await api()
setState()
}
function handleClickButton1 () {
trigger().then(()=> setInput1('button1'))
}
function handleClickButton2 () {
trigger().then(()=> setInput1('button2'))
}
Compare that to the before schema:
- Remove the state
isClickButton1
,isClickButton2
- Move the
onComplete
from the fetch trait to thebutton1
andbutton2
function trigger () {
api().then(()=> {
setState()
if (isClickButton1) {
setInput1('button1')
}
if (isClickButton2) {
setInput1('button2')
}
setIsClickButton1(false)
setIsClickButton2(false)
}).catch(()=> {
setIsClickButton1(false)
setIsClickButton2(false)
})
}
function handleClickButton1 () {
setIsClickButton1(true)
trigger()
}
function handleClickButton2 () {
setIsClickButton2(true)
trigger()
}
Why do these two buttons share the same trigger
, instead of
function trigger1 () {
api().then(()=> {
setInput1('button1')
})
}
function trigger2 () {
api().then(()=> {
setInput2('button2')
})
}
function handleClickButton1 () {
trigger1()
}
function handleClickButton2 () {
trigger2()
}
Why do these two buttons share the same trigger
Because they use the same fetch trait to call the same API, they use the same trigger
. However, the things the buttons need to do in onComplete
is different.
Do you mean to use the different fetch traits to call the same API and then they can use the different onComplete
to solve the problem?
They can't reuse the parameters and some common logic and so on if use the different fetch traits to call the same API. I think this isn't the best way to solve the problem.