问题描述
我实际上正在尝试使用fastify的API进行反应保持功能.
I'm actually trying the power of react-ketting with a fastify's API.
从钩子useResource上,我可以获取一个将进行POST的Submit函数,但是我不知道该提交何时完成,并且无法获得该POST的API答复.
From the hook useResource, I can get a submit function that will make a POST but I can't know when that submit is done and I can't get the API reply of this POST.
反应不允许这样做吗?我是否错过了参数,还是必须使用其他类似Axios的PUT/POST才能拥有全部控制权?
React doesn't allow this? Did I miss a param or must I use something else for PUT/POST like axios in order to have all control ?
// 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>
);
};
推荐答案
使用Ketting进行POST请求的主要原因/过程有两个.
There's two main reasons/processes to do POST requests with Ketting.
- 要创建新资源/将新资源添加到集合中.
- 进行任意的RPC调用/提交表单.
创建新资源
使用 useResource()
钩子并使用 submit()
函数时,此方法的主要目的是处理第一种情况.
Creating a new resource
When you use the useResource()
hook, and use the submit()
function, the primary purpose of this is to deal with the first case.
因为您严格地在制作新资源,所以期望该响应包含:
Because you are strictly making a new resource, the expectation is that the response contains:
-
201已创建
状态 - 指向新创建的资源的
Location
标头.
(可选)服务器可以返回新创建的资源的主体,但为此,服务器还必须包含 Content-Location
标头.
Optionally, you the server can return the body of the newly created resource, but to do that, the server must also include a Content-Location
header.
如果这是您的目的,则只需侦听您已经拥有的组件中的状态变化,就可以得到 POST
请求的结果.
If that was your purpose, you can get the result of the POST
request simply by listening to state changes in the component you already have.
如果您属于第二类,并且只想执行任意POST请求并读取响应,则应该不使用从代码返回的 submit()
函数钩子.
If you are in the second category and just want to do an arbitrary POST request and read the response, you should not use the submit()
function returned from the hook.
useResource
钩子上的 submit()
函数实际上用作资源状态提交"机制,对于其他任意 POST
请求.
The submit()
function on the useResource
hook is really used as a 'resource state submissions' mechanism, and it's not good to overload this for other arbitrary POST
requests.
相反,只需在 Resource
本身上使用 .post()
函数.
Instead, just use the .post()
function on the Resource
itself.
const response = await props.resource.post({
data: body
});
这篇关于如何在完成发布后获得通知的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!