繁体   English   中英

如何在帖子完成时收到通知

[英]How to get notified when a Post is done with ketting

我实际上是在尝试使用 fastify 的 API 的反应力。

从钩子 useResource 中,我可以得到一个提交 function ,它将进行 POST,但我不知道该提交何时完成,并且我无法获得此 POST 的 API 回复。

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>
  );
};

使用 Ketting 进行 POST 请求有两个主要原因/过程。

  1. 创建新资源/将新资源添加到集合中。
  2. 进行任意 RPC 调用/提交表单。

创建新资源

当您使用useResource()钩子并使用submit() function 时,这样做的主要目的是处理第一种情况。

因为您正在严格地制作新资源,所以期望响应包含:

  1. A 201 Created状态
  2. 一个Location header 指向新创建的资源。

或者,服务器可以返回新创建资源的主体,但要做到这一点,服务器还必须包含Content-Location header。

如果那是您的目的,您可以通过侦听您已有的组件中的 state 更改来获得POST请求的结果。

执行 RPC POST 请求/提交表单

如果您属于第二类并且只想执行任意 POST 请求并读取响应,则不应使用从钩子返回的submit() function。

useResource挂钩上的submit() function 确实用作“资源 state 提交”机制,对于其他任意POST请求,重载它是不好的。

相反,只需在Resource本身上使用.post() function 即可。

const response = await props.resource.post({
  data: body
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM