plone / volto

React-based frontend for the Plone Content Management System
https://demo.plone.org/
MIT License
453 stars 612 forks source link

`@plone/client` - Modernize Data Fetching API #4347

Open sneridagh opened 1 year ago

sneridagh commented 1 year ago

PLIP (Plone Improvement Proposal)

Responsible Persons

Proposer: Víctor Fernández de Alba (@sneridagh)

Seconder:

Abstract

Modernize the data fetching story in Volto by deprecating AsyncConnect library and moving the Server State handling to TanStack Query. As a result, provide an agnostic library of actions called @plone/client that could be reused not only in Volto but in other React frameworks or JS apps. It will be the standard de-facto plone.restapi React client for Plone.

Motivation

New libraries making extensive use of hooks and advanced capabilities like caching techniques, retriability, abort, etc... are becoming the "golden standard" in the React Ecosystem. Nowadays, we use Redux and a custom-made Redux middleware to keep server-state in Volto. Once upon a time, this was the golden standard way of doing things, but modern libraries does the job more efficiently, they provide better developer experience and they do not require that much boilerplate to use them.

We should leave Redux to take care of Application State only.

AsyncConnect is an abandoned library and although we adopted it in the past, we should get rid of it and make use of a supported library instead. We should replace it with the SSR part of react-query providing a way to require in the backend of all the data in SSR that Volto might require. We will do that in a pluggable way, so it can be extended easily.

Assumptions

No assumptions in advance.

Proposal & Implementation

We will create a package @plone/client that will contain the actions implemented with (TanStack Query)[https://tanstack.com/query] best practices.

// ⬇️ define your query
const contactDetailQuery = (id) => ({
  queryKey: ['contacts', 'detail', id],
  queryFn: async () => getContact(id),
})

export default function Contact() {
  const params = useParams()
  // ⬇️ useQuery
  const { data: contact } = useQuery(contactDetailQuery(params.contactId))
  // render some jsx
}

We will make extensive use of the queryKey using the path to identify requests. We will need a way to generalize the params of the query, so we keep DRY.

Initially will assume we continue using superagent for the actual data fetching.

We will provide a way to inject new initial "bootstrap" queries path dependent. They will be prefetched and injected in the react-query cache in SSR, rehydrated in the client later. Initially, it should load the content query, with all the default APIExpanders.

PoC - @plone/client

Existing PoC: https://github.com/plone/volto/pull/3406 Existing work already in @plone/client https://github.com/plone/plone.restapi-client

Introduce the new fetch API to the Volto components

Needs discussion: Providing the functionality and make them available to the components is "easy". The difficult part comes in deciding how to apply them in default Volto components. Ideally, because of the nature of the change and since we are replacing on of the sensible architecture fundamentals in Volto, the approach should be an "all-in" solution.

In this scenario, we provide a whole new breed of components (@plone/components #4352 components PLIP), aligned with this PLIP.

The main downside to this is that all the existing shadows will break(*).

(*) Could be that we could find a way that a shadow with the Redux API actions still work, but we will have to leave in place all the Redux actions/reducers for the old actions and the SSR part (AsyncConnect). This could produce confusion to developers and newbies, and also a difficult thing to document and teach. Also, it will provoke that the SSR part complicates in a horrible way and we will be unable to deprecate AsyncConnect.

Right now, I can't see any other way, but let's discuss it thoroughly.

Incremental adoption

We can start developing @plone/client, and start using it outside Volto. At some point, when we have them all ready, we can make the component change. In the case that we want to start using it in also in custom Volto components, we would need to put in place temporarily both react-query SSR and AsyncConnect SSR side by side, though.

Deliverables

Risks

All existing shadows on old components that are doing data fetching will break(*).

Result

This PLIP was part of the GSOC2023 and you can find the (ongoing) outcome of the project in:

https://github.com/plone/plone.restapi-client

Participants

Víctor Fernández de Alba (Leading) Hemant Chaudhary (@hemant-hc) (GSOC student)

stevepiercy commented 1 year ago

@sneridagh should this issue be closed then?

sneridagh commented 1 year ago

@stevepiercy still a lot of things to do in the client side, and the integration in Volto is still due.

We have to make the decision of leave this open, or open a new PLIP with the rest of the tasks.

sneridagh commented 9 months ago

5490 Implemented and in alpha state. Leaving the PLIP open for further developments and to mark that it's not finished yet!

https://github.com/plone/volto/blob/main/packages/client/README.md