如何在 Post 完成 ketting 时收到通知
How to get notified when a Post is done with ketting
我实际上是在用 fastify 的 API 来尝试 react-ketting 的力量。
从挂钩 useResource 中,我可以获得一个提交函数,该函数将生成一个 POST,但我不知道该提交何时完成,而且我无法获得 API 的回复这个POST.
React 不允许这样做?我错过了一个参数还是我必须为 PUT/POST 使用其他东西,比如 axios 才能拥有所有控制权?
// API - with Fastify
// server.js
// Require the framework and instantiate it
const fastify = require('fastify')({
logger: true
})
fastify.register(require('fastify-cors'), {
origin: true,
allowedHeaders: [
'Content-Type',
'User-Agent',
'Authorization',
'Accept',
'Prefer',
'Link'
],
methods: [
'DELETE',
'GET',
'PATCH',
'POST',
'PUT',
'HEAD'
],
exposedHeaders: [
'Location',
'Link'
]
});
// Loading routes
fastify.register(require('./routes/Products'));
// Run the server!
fastify.listen(5000, '0.0.0.0', function (err, address) {
if (err)
{
fastify.log.error(err)
process.exit(1)
}
fastify.log.info(`server listening on ${address}`)
});
// ./routes/Products.js
async function routes(fastify, options) {
// Root
fastify.get('/', async (request, reply) => {
// Relations
resource
.addLink('collection', { href: `/products`, title: 'Products' });
reply.send(resource);
});
// List all products
fastify.get('/products', async (request, reply) => {
const resource = halson();
// Relations
resource
.addLink('self', { href: '/products', title: 'Products' })
// Collection items
const products = DB.getProducts(); // [{id: 'xxx', name: 'yyy'}, ...]
if (products && products.length) {
products.forEach(product => {
resource.addLink('item', { href: `/products/${product.id}`, title: product.name });
});
}
// Actions like
resource.addLink('create', { href: '/products/add', title: 'Add product' })
reply.send(resource);
})
// Get product
fastify.get('/products/:productId', async (request, reply) => {
const productId = request.params.productId;
const product = DB.getProductById(productId); // { id: 'xxx', name: 'yyy', ... }
const resource = halson(product);
// Relations
resource
.addLink('self', { href: `/products/${productId}`, title: `${product.name}` })
reply.send(resource);
});
// Get add product form
fastify.get('/products/add', async (request, reply) => {
const resource = halson();
// Relations
resource
.addLink('create-form', { href: 'addProductForm' })
// Embeded resource
const initialData = {
productId: uuidv4(),
name: ''
};
const embed = halson({
submitData: {
resource: '/products/add',
mode: 'POST',
initialData: initialData,
},
jsonSchema: {
type: 'object',
title: 'Add a product',
required: ['name'],
properties: {
productId: {
type: 'string',
title: 'Product ID'
},
name: {
type: 'string',
title: 'Product name'
}
}
},
uiSchema: {
productId: {
"ui:readonly": true
},
name: {
"ui:autofocus": true
}
},
formData: initialData
});
embed.addLink('self', { href: 'addProductForm' })
resource.addEmbed('create-form', embed);
reply.send(resource);
});
// Post new product
fastify.post('/products/add', async (request, reply) => {
const product = DB.addProduct(request.body); // { id: 'xxx', name: 'yyy', ... }
reply
.code(201)
.header('Location', `/products/${product.id}`)
.send(halson());
});
}
// Front
// index.jsx
import { Client } from 'ketting';
import { KettingProvider } from 'react-ketting';
const BASE_URL = 'http://localhost:5000';
const KettingClient = new Client(BASE_URL);
// HOC useResource
const withUseResource = WrappedComponent => ({ resource, ...props }) => {
const {
loading,
error,
data,
setData,
submit,
resourceState,
setResourceState,
} = useResource(resource);
if (loading) return 'Loading ...';
if (error) return `Error : ${error.message}`;
const { children } = props;
const newProps = {
resource,
resourceData: data,
setResourceData: setData,
submitResourceData: submit,
resourceState,
setResourceState,
};
return (
<WrappedComponent {...newProps} {...props}>
{children}
</WrappedComponent>
);
};
// HOC useCollection
const withUseCollection = WrappedComponent => ({ resource, ...props }) => {
const [collection, setCollection] = useState([]);
const { loading, error, items } = useCollection(resource);
useEffect(() => {
setCollection(items);
return () => {
setCollection([]);
};
}, [items]);
if (loading) return 'Loading ...';
if (error) return `Error : ${error.message}`;
const { children } = props;
const newProps = {
resource,
collection,
};
return (
<WrappedComponent {...newProps} {...props}>
{children}
</WrappedComponent>
);
};
// Main Component
function Root() {
return (
<KettingProvider client={KettingClient}>
<Container />
</KettingProvider>
);
}
function Container() {
const [url, setUrl] = useState(`${BASE_URL}/`);
const [resource, setResource] = useState(null);
useEffect(() => {
setResource(KettingClient.go(url));
}, [url]);
if (!resource) {
return null;
}
return <Content resource={resource} />;
}
const SimpleContent = ({ resource, resourceState }) => {
let content = null;
if (resourceState.links.has('collection')) {
content = <Catalog resource={resource.follow('collection')} />;
}
return content;
};
const Content = withUseResource(SimpleContent);
const SimpleCatalog = ({ resource, resourceState, collection }) => {
const [addProductBtn, setAddProductBtn] = useState(null);
useEffect(() => {
if (resourceState?.links.has('create')) {
setAddProductBtn(
<AddNewProduct resource={resource.follow('create')} />
);
}
}, []);
return (
<>
{collection.map((item, index) => (
<Product key={index} resource={item} />
))}
{addProductBtn}
</>
);
};
const Catalog = withUseResource(withUseCollection(SimpleCatalog));
const SimpleProduct = ({ resourceData }) => {
return <p>{resourceData.name}</p>;
};
const Product = withUseResource(SimpleProduct);
const SimpleNewProduct = ({ resource, resourceState }) => {
const [openDialog, setOpenDialog] = useState(false);
const [dialogConfig, setDialogConfig] = useState({});
useEffect(() => {
if (resourceState.links.has('create-form')) {
setDialogConfig({
title: '',
content: (
<div>
<FormDialog
resource={resource.follow('create-form')}
setOpenDialog={setOpenDialog}
/>
</div>
),
actions: '',
});
}
return () => {
setDialogConfig({});
};
}, []);
return (
<>
<Button onClick={() => setOpenDialog(true)}>+</Button>
<PopupDialog
config={dialogConfig}
open={openDialog}
onClose={() => setOpenDialog(false)}
/>
</>
);
};
const AddNewProduct = withUseResource(SimpleNewProduct);
const SimpleFormDialog = ({ resourceData, setOpenDialog }) => {
const { jsonSchema, uiSchema, formData, submitData } = resourceData;
const { loading, error, setData, submit } = useResource(
submitData
);
if (loading) return 'Loading ...';
if (error) return `Error : ${error.message}`;
const handleChange = fd => {
setData(fd);
};
const handleSubmit = () => {
// How to get notified that the post has been processed,
// so that I can go to the new product page,
// or have the catalog to be refreshed ?
submit();
setOpenDialog(false);
};
return (
<div>
{ /* React JsonSchema Form */ }
<RjsfForm
JsonSchema={jsonSchema}
UiSchema={uiSchema}
formDataReceived={formData}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
</div>
);
};
const FormDialog = withUseResource(SimpleFormDialog);
const PopupDialog = ({ open, onClose, config }) => {
if (!config) return null;
const { title, content, actions, props } = config;
return (
<Dialog fullWidth maxWidth='md' open={open} onClose={onClose} {...props}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{content}</DialogContent>
<DialogActions>{actions}</DialogActions>
</Dialog>
);
};
有两个主要 reasons/processes 需要与 Ketting 一起完成 POST 请求。
- 创建新资源/将新资源添加到 collection。
- 执行任意 RPC 调用/提交表单。
正在创建新资源
当你使用useResource()
钩子,并使用submit()
函数时,这样做的主要目的是处理第一种情况。
因为您是严格制作新资源,所以期望响应包含:
- 一个
201 Created
状态
- A
Location
header 指向新创建的资源。
可选地,您的服务器可以 return 新创建资源的 body,但要做到这一点,服务器还必须包含 Content-Location
header。
如果那是您的目的,您只需监听已有组件的状态变化即可获得 POST
请求的结果。
执行 RPC POST 请求/提交表单
如果您属于第二类并且只想执行任意 POST 请求并读取响应,则您应该 而不是 使用 submit()
函数 return 从钩子中编辑。
useResource
钩子上的 submit()
函数实际上用作 'resource state submissions' 机制,对于其他任意 POST
请求重载它是不好的。
相反,只需在 Resource
本身上使用 .post()
函数。
const response = await props.resource.post({
data: body
});
我实际上是在用 fastify 的 API 来尝试 react-ketting 的力量。
从挂钩 useResource 中,我可以获得一个提交函数,该函数将生成一个 POST,但我不知道该提交何时完成,而且我无法获得 API 的回复这个POST.
React 不允许这样做?我错过了一个参数还是我必须为 PUT/POST 使用其他东西,比如 axios 才能拥有所有控制权?
// API - with Fastify
// server.js
// Require the framework and instantiate it
const fastify = require('fastify')({
logger: true
})
fastify.register(require('fastify-cors'), {
origin: true,
allowedHeaders: [
'Content-Type',
'User-Agent',
'Authorization',
'Accept',
'Prefer',
'Link'
],
methods: [
'DELETE',
'GET',
'PATCH',
'POST',
'PUT',
'HEAD'
],
exposedHeaders: [
'Location',
'Link'
]
});
// Loading routes
fastify.register(require('./routes/Products'));
// Run the server!
fastify.listen(5000, '0.0.0.0', function (err, address) {
if (err)
{
fastify.log.error(err)
process.exit(1)
}
fastify.log.info(`server listening on ${address}`)
});
// ./routes/Products.js
async function routes(fastify, options) {
// Root
fastify.get('/', async (request, reply) => {
// Relations
resource
.addLink('collection', { href: `/products`, title: 'Products' });
reply.send(resource);
});
// List all products
fastify.get('/products', async (request, reply) => {
const resource = halson();
// Relations
resource
.addLink('self', { href: '/products', title: 'Products' })
// Collection items
const products = DB.getProducts(); // [{id: 'xxx', name: 'yyy'}, ...]
if (products && products.length) {
products.forEach(product => {
resource.addLink('item', { href: `/products/${product.id}`, title: product.name });
});
}
// Actions like
resource.addLink('create', { href: '/products/add', title: 'Add product' })
reply.send(resource);
})
// Get product
fastify.get('/products/:productId', async (request, reply) => {
const productId = request.params.productId;
const product = DB.getProductById(productId); // { id: 'xxx', name: 'yyy', ... }
const resource = halson(product);
// Relations
resource
.addLink('self', { href: `/products/${productId}`, title: `${product.name}` })
reply.send(resource);
});
// Get add product form
fastify.get('/products/add', async (request, reply) => {
const resource = halson();
// Relations
resource
.addLink('create-form', { href: 'addProductForm' })
// Embeded resource
const initialData = {
productId: uuidv4(),
name: ''
};
const embed = halson({
submitData: {
resource: '/products/add',
mode: 'POST',
initialData: initialData,
},
jsonSchema: {
type: 'object',
title: 'Add a product',
required: ['name'],
properties: {
productId: {
type: 'string',
title: 'Product ID'
},
name: {
type: 'string',
title: 'Product name'
}
}
},
uiSchema: {
productId: {
"ui:readonly": true
},
name: {
"ui:autofocus": true
}
},
formData: initialData
});
embed.addLink('self', { href: 'addProductForm' })
resource.addEmbed('create-form', embed);
reply.send(resource);
});
// Post new product
fastify.post('/products/add', async (request, reply) => {
const product = DB.addProduct(request.body); // { id: 'xxx', name: 'yyy', ... }
reply
.code(201)
.header('Location', `/products/${product.id}`)
.send(halson());
});
}
// Front
// index.jsx
import { Client } from 'ketting';
import { KettingProvider } from 'react-ketting';
const BASE_URL = 'http://localhost:5000';
const KettingClient = new Client(BASE_URL);
// HOC useResource
const withUseResource = WrappedComponent => ({ resource, ...props }) => {
const {
loading,
error,
data,
setData,
submit,
resourceState,
setResourceState,
} = useResource(resource);
if (loading) return 'Loading ...';
if (error) return `Error : ${error.message}`;
const { children } = props;
const newProps = {
resource,
resourceData: data,
setResourceData: setData,
submitResourceData: submit,
resourceState,
setResourceState,
};
return (
<WrappedComponent {...newProps} {...props}>
{children}
</WrappedComponent>
);
};
// HOC useCollection
const withUseCollection = WrappedComponent => ({ resource, ...props }) => {
const [collection, setCollection] = useState([]);
const { loading, error, items } = useCollection(resource);
useEffect(() => {
setCollection(items);
return () => {
setCollection([]);
};
}, [items]);
if (loading) return 'Loading ...';
if (error) return `Error : ${error.message}`;
const { children } = props;
const newProps = {
resource,
collection,
};
return (
<WrappedComponent {...newProps} {...props}>
{children}
</WrappedComponent>
);
};
// Main Component
function Root() {
return (
<KettingProvider client={KettingClient}>
<Container />
</KettingProvider>
);
}
function Container() {
const [url, setUrl] = useState(`${BASE_URL}/`);
const [resource, setResource] = useState(null);
useEffect(() => {
setResource(KettingClient.go(url));
}, [url]);
if (!resource) {
return null;
}
return <Content resource={resource} />;
}
const SimpleContent = ({ resource, resourceState }) => {
let content = null;
if (resourceState.links.has('collection')) {
content = <Catalog resource={resource.follow('collection')} />;
}
return content;
};
const Content = withUseResource(SimpleContent);
const SimpleCatalog = ({ resource, resourceState, collection }) => {
const [addProductBtn, setAddProductBtn] = useState(null);
useEffect(() => {
if (resourceState?.links.has('create')) {
setAddProductBtn(
<AddNewProduct resource={resource.follow('create')} />
);
}
}, []);
return (
<>
{collection.map((item, index) => (
<Product key={index} resource={item} />
))}
{addProductBtn}
</>
);
};
const Catalog = withUseResource(withUseCollection(SimpleCatalog));
const SimpleProduct = ({ resourceData }) => {
return <p>{resourceData.name}</p>;
};
const Product = withUseResource(SimpleProduct);
const SimpleNewProduct = ({ resource, resourceState }) => {
const [openDialog, setOpenDialog] = useState(false);
const [dialogConfig, setDialogConfig] = useState({});
useEffect(() => {
if (resourceState.links.has('create-form')) {
setDialogConfig({
title: '',
content: (
<div>
<FormDialog
resource={resource.follow('create-form')}
setOpenDialog={setOpenDialog}
/>
</div>
),
actions: '',
});
}
return () => {
setDialogConfig({});
};
}, []);
return (
<>
<Button onClick={() => setOpenDialog(true)}>+</Button>
<PopupDialog
config={dialogConfig}
open={openDialog}
onClose={() => setOpenDialog(false)}
/>
</>
);
};
const AddNewProduct = withUseResource(SimpleNewProduct);
const SimpleFormDialog = ({ resourceData, setOpenDialog }) => {
const { jsonSchema, uiSchema, formData, submitData } = resourceData;
const { loading, error, setData, submit } = useResource(
submitData
);
if (loading) return 'Loading ...';
if (error) return `Error : ${error.message}`;
const handleChange = fd => {
setData(fd);
};
const handleSubmit = () => {
// How to get notified that the post has been processed,
// so that I can go to the new product page,
// or have the catalog to be refreshed ?
submit();
setOpenDialog(false);
};
return (
<div>
{ /* React JsonSchema Form */ }
<RjsfForm
JsonSchema={jsonSchema}
UiSchema={uiSchema}
formDataReceived={formData}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
</div>
);
};
const FormDialog = withUseResource(SimpleFormDialog);
const PopupDialog = ({ open, onClose, config }) => {
if (!config) return null;
const { title, content, actions, props } = config;
return (
<Dialog fullWidth maxWidth='md' open={open} onClose={onClose} {...props}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{content}</DialogContent>
<DialogActions>{actions}</DialogActions>
</Dialog>
);
};
有两个主要 reasons/processes 需要与 Ketting 一起完成 POST 请求。
- 创建新资源/将新资源添加到 collection。
- 执行任意 RPC 调用/提交表单。
正在创建新资源
当你使用useResource()
钩子,并使用submit()
函数时,这样做的主要目的是处理第一种情况。
因为您是严格制作新资源,所以期望响应包含:
- 一个
201 Created
状态 - A
Location
header 指向新创建的资源。
可选地,您的服务器可以 return 新创建资源的 body,但要做到这一点,服务器还必须包含 Content-Location
header。
如果那是您的目的,您只需监听已有组件的状态变化即可获得 POST
请求的结果。
执行 RPC POST 请求/提交表单
如果您属于第二类并且只想执行任意 POST 请求并读取响应,则您应该 而不是 使用 submit()
函数 return 从钩子中编辑。
useResource
钩子上的 submit()
函数实际上用作 'resource state submissions' 机制,对于其他任意 POST
请求重载它是不好的。
相反,只需在 Resource
本身上使用 .post()
函数。
const response = await props.resource.post({
data: body
});