Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: RealTimeQueries component #742

Merged
merged 2 commits into from
Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- '8'
- '10'
matrix:
fast_finish: true
cache:
Expand Down
23 changes: 22 additions & 1 deletion docs/react-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ The following props will be given to your wrapped component:
- `hasMore`: the fetches being paginated, this property indicates if there are more documents to load


You can also pass a function intead of a direct query. Your function will be given the props of the component and should return the requested query:
You can also pass a function instead of a direct query. Your function will be given the props of the component and should return the requested query:

```jsx
import { Query } from 'cozy-client'
Expand Down Expand Up @@ -253,6 +253,27 @@ queryConnect({
See [CozyClient::fetchPolicies](https://docs.cozy.io/en/cozy-client/api/cozy-client/#CozyClient.fetchPolicies)
for the API documentation.

### 2.d Keeping data up to date in real time

Sometimes the data you are displaying will be changed from other places than your app. Maybe the data is shared and someone else has updated it, or maybe it simply changes over time.

You can however keep your UI always up to date without constantly re-running your queries, by subscribing to changes in real time. This is done with the `RealTimeQueries` component:

```jsx
import { RealTimeQueries } from 'cozy-client'

function MyParentComponent(props) {
return (
<>
<ConnectedTodoList +>
<RealTimeQueries doctype="io.cozy.todos" />
</>
)
}
```

You subscribe to changes for an entire doctype using `RealTimeQueries`, and as long as that component is rendered all documents from the given doctype in your queries will automatically stay up to date.

### 3. Mutating data

The simplest way is to use the `withClient` high order component. It will inject a `client` in your props with the CozyClient instance you gave to the `<CozyProvider />` upper.
Expand Down
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const commonConfig = {
'<rootDir>/packages/cozy-stack-client/src/__tests__/setup.js'
],
modulePathIgnorePatterns: ['<rootDir>/packages/.*/dist/'],
transformIgnorePatterns: ['node_modules/(?!(cozy-ui))']
transformIgnorePatterns: ['node_modules/(?!(cozy-ui))'],
testEnvironment: 'jest-environment-jsdom-sixteen'
ptbrowne marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"eslint-plugin-react": "7.18.3",
"husky": "0.14.3",
"jest": "24.9.0",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-fetch-mock": "1.7.5",
"jsdoc-to-markdown": "4.0.1",
"lerna": "3.20.2",
Expand Down
1 change: 1 addition & 0 deletions packages/cozy-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@babel/cli": "7.6.2",
"@cozy/codemods": "^1.1.0",
"@testing-library/react-hooks": "^3.2.1",
"@testing-library/react": "^10.0.1",
"babel-plugin-search-and-replace": "1.0.1",
"btoa": "1.2.1",
"cozy-logger": "1.6.0",
Expand Down
60 changes: 60 additions & 0 deletions packages/cozy-client/src/RealTimeQueries.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { memo, useEffect } from 'react'
import useClient from './hooks/useClient'
import { file as fileModel } from './models'
import { Mutations } from './queries/dsl'
import { receiveMutationResult } from './store'

const dispatchChange = (client, document, mutationDefinitionCreator) => {
const response = {
data: fileModel.normalize(document)
}

const options = {}
client.dispatch(
receiveMutationResult(
client.generateId(),
response,
options,
mutationDefinitionCreator(document)
)
)
}

const RealTimeQueries = ({ doctype }) => {
const client = useClient()

useEffect(() => {
const realtime = client.plugins.realtime

const dispatchCreate = document => {
dispatchChange(client, document, Mutations.createDocument)
}
const dispatchUpdate = document => {
dispatchChange(client, document, Mutations.updateDocument)
}
const dispatchDelete = document => {
dispatchChange(
client,
{ ...document, _deleted: true },
Mutations.deleteDocument
)
}

const subscribe = async () => {
await realtime.subscribe('created', doctype, dispatchCreate)
await realtime.subscribe('updated', doctype, dispatchUpdate)
await realtime.subscribe('deleted', doctype, dispatchDelete)
}
subscribe()

return () => {
realtime.unsubscribe('created', doctype, dispatchCreate)
realtime.unsubscribe('updated', doctype, dispatchUpdate)
realtime.unsubscribe('deleted', doctype, dispatchDelete)
}
}, [client, doctype])

return null
}

export default memo(RealTimeQueries)
89 changes: 89 additions & 0 deletions packages/cozy-client/src/RealTimeQueries.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react'
import { render, waitFor } from '@testing-library/react'
import RealTimeQueries from './RealTimeQueries'
import { createMockClient } from './mock'
import CozyProvider from './Provider'

describe('RealTimeQueries', () => {
it('notifies the cozy-client store', async () => {
const realtimeCallbacks = {}
const client = new createMockClient({})
client.plugins.realtime = {
subscribe: jest.fn((event, doctype, callback) => {
realtimeCallbacks[event] = callback
}),
unsubscribe: jest.fn()
}
client.dispatch = jest.fn()

const { unmount } = render(
<CozyProvider client={client}>
<RealTimeQueries doctype="io.cozy.files" />
</CozyProvider>
)

await waitFor(() =>
expect(client.plugins.realtime.subscribe).toHaveBeenCalledTimes(3)
)

realtimeCallbacks['created']({ id: 'mock-created', type: 'file' })
expect(client.dispatch).toHaveBeenCalledWith(
expect.objectContaining({
definition: {
document: { id: 'mock-created', type: 'file' },
mutationType: 'CREATE_DOCUMENT'
},
mutationId: 1,
response: {
data: {
_type: 'io.cozy.files',
type: 'file',
id: 'mock-created',
_id: 'mock-created'
}
},
type: 'RECEIVE_MUTATION_RESULT'
})
)

realtimeCallbacks['updated']({ id: 'mock-updated', type: 'file' })
expect(client.dispatch).toHaveBeenCalledWith({
definition: {
document: { id: 'mock-updated', type: 'file' },
mutationType: 'UPDATE_DOCUMENT'
},
mutationId: 2,
response: {
data: {
_type: 'io.cozy.files',
type: 'file',
id: 'mock-updated',
_id: 'mock-updated'
}
},
type: 'RECEIVE_MUTATION_RESULT'
})

realtimeCallbacks['deleted']({ id: 'mock-deleted', type: 'file' })
expect(client.dispatch).toHaveBeenCalledWith({
definition: {
document: { id: 'mock-deleted', type: 'file', _deleted: true },
mutationType: 'DELETE_DOCUMENT'
},
mutationId: 3,
response: {
data: {
_deleted: true,
_type: 'io.cozy.files',
type: 'file',
id: 'mock-deleted',
_id: 'mock-deleted'
}
},
type: 'RECEIVE_MUTATION_RESULT'
})

unmount()
expect(client.plugins.realtime.unsubscribe).toHaveBeenCalledTimes(3)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It'd be nice to be able to check if the realtime doesn't have any listener anymore. Here the 3 is hardcoded and will need to change with the implementation. I don't think that's possible with the realtime current API though.

})
})
1 change: 1 addition & 0 deletions packages/cozy-client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { dehydrate, generateWebLink } from './helpers'
export { cancelable, isQueryLoading, hasQueryBeenLoaded } from './utils'
export { getQueryFromState } from './store'
export { default as Registry } from './registry'
export { default as RealTimeQueries } from './RealTimeQueries'

import * as manifest from './manifest'
export { manifest }
Expand Down
Loading