Skip to content
This repository has been archived by the owner on Jul 17, 2023. It is now read-only.

SWC-5612 - Store application-wide data (including auth token) in context #1047

Merged
merged 14 commits into from Jun 2, 2021

Conversation

nickgros
Copy link
Contributor

@nickgros nickgros commented May 20, 2021

Summary of changes

These changes let us pull the authentication token (as well as some other fields) from context, instead of passing them through props. This dramatically cleans up the code by eliminating some excessive prop drilling. This also reduces the risk of error: it was easy to not pass a token via props since it was optional in most contexts. Now the token, is only accessed when it is used.

This will be a breaking change. Any application that uses SRC will need to wrap all SRC components in a SynapseContextProvider.

The application data provided via this context currently includes these values:

  • access token (which remains undefined if the user is not logged in)
  • whether time should be displayed in UTC
  • whether experimental mode is on

The context also provides a react-query QueryClient, so our components can now utilize a shared cache when using react-query.

This can be extended to provide more data fairly easily, if the need arises.

A note on testing

I discovered that Enzyme doesn't fully support the React 16+ context API (see Enzyme React 16 support and enzymejs/enzyme#2189). In particular, contextType (which is used exclusively on class components) doesn't work with the shallow renderer. I rewrote tests for a few components to use mount, but for other class components, I left the access token as a prop. An access token can still be passed to the component via context using a SynapseContextConsumer. Additionally, components accepting access tokens as a prop must still be wrapped in context in live settings, because they may have child components that access context.

Tangentially, Facebook no longer uses Enzyme in any new tests. I think we should consider at least avoiding the shallow renderer (we can still use Jest mocks to reduce the test surface of complex components).

Checklist

For follow up:

  • Investigate removing QueryClient wrappers from other components (e.g. EntityFinder, new download list).

@nickgros nickgros changed the title Store application-wide data (including auth token) in context SWC-5612 - Store application-wide data (including auth token) in context May 21, 2021
@nickgros nickgros marked this pull request as ready for review May 28, 2021 15:19
Comment on lines +22 to +38
jest.mock('../../../lib/containers/widgets/SynapsePlot', () => {
return {
__esModule: true,
default: function MockSynapsePlot() {
return <div role="figure"></div>
},
}
})

jest.mock('../../../lib/containers/widgets/SynapseImage', () => {
return {
__esModule: true,
default: function MockSynapseImage() {
return <div role="img"></div>
},
}
})
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simple example for how we can use mocks to reduce testing surface while using RTL's render or Enzyme's mount

Comment on lines +1 to +28
import React from 'react'
import {
SynapseContextProvider,
SynapseContextType,
} from '../lib/utils/SynapseContext'

export const MOCK_ACCESS_TOKEN = 'mock-access-token'

export const MOCK_CONTEXT_VALUE: SynapseContextType = {
accessToken: MOCK_ACCESS_TOKEN,
utcTime: false,
isInExperimentalMode: false,
}

export const MOCK_CONTEXT = React.createContext(MOCK_CONTEXT_VALUE)

/**
* Full context object with default values for testing.
*/
export const SynapseTestContext = jest
.fn()
.mockImplementation(({ children }) => {
return (
<SynapseContextProvider synapseContext={MOCK_CONTEXT_VALUE}>
{children}
</SynapseContextProvider>
)
})
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handy prebuilt pieces to use across all tests.

*
* Adapted from https://medium.com/@jrwebdev/react-higher-order-component-patterns-in-typescript-42278f7590fb
*/
export const withQueryClientProvider = <P extends object>(
Copy link
Contributor Author

@nickgros nickgros May 28, 2021

Choose a reason for hiding this comment

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

No longer needed as we now require all components to be wrapped in SynapseContextProvider, which itself contains a QueryClientProvider

* @param param0
* @returns
*/
export const SynapseContextProvider: React.FunctionComponent<SynapseContextProviderProps> = ({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Optional reading: here's a blog post by Kent C. Dodds that has guided many of the design decisions made here.

Comment on lines +13 to +23
/**
* TODO: SWC-5612 - Replace token prop with SynapseContext.accessToken
*
* This wasn't done because Enzyme's shallow renderer is not currently
* compatible with the `contextType` field in the React 16+ context API.
*
* This can be fixed by rewriting tests to not rely on the shallow renderer.
*
* See here: https://github.com/enzymejs/enzyme/issues/1553
*/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

e.g. comment I left on the few classes I didn't want to rewrite tests for

Comment on lines 24 to 35
export default function useGetEntityBundle(
accessToken: string,
entityId: string,
bundleRequest: EntityBundleRequest = ALL_FIELDS,
version?: number,
options?: UseQueryOptions<EntityBundle, SynapseClientError, EntityBundle>,
) {
const { accessToken } = useSynapseContext()
return useQuery<EntityBundle, SynapseClientError>(
['entitybundle', entityId, version, bundleRequest],
[accessToken, 'entitybundle', entityId, version, bundleRequest],
() =>
SynapseClient.getEntityBundleV2(
entityId,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is awesome: this query function is a hook itself, so we can call useContext here. The caller of useGetEntityBundle doesn't even need the access token!

Comment on lines +4 to +11
const defaultQueryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30 * 1000, // 30s
retry: false, // SynapseClient knows which queries to retry
},
},
})
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved this from QueryClientWrapper, we'll provide it here.

const context = useContext(SynapseContext)
if (context === undefined) {
throw new Error(
'useSynapseContext must be used within a SynapseContextProvider',
Copy link
Member

Choose a reason for hiding this comment

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

like it, fail fast

*
* This can be fixed by rewriting tests to not rely on the shallow renderer.
*
* See here: https://github.com/enzymejs/enzyme/issues/1553
Copy link
Member

Choose a reason for hiding this comment

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

ok

@jay-hodgson
Copy link
Member

Fully reviewed SynapseContext.tsx, useEntityBundle.ts, HasAccess.tsx, FacetNavPanel.tsx.
As well as README.md, SynapseClient.ts, and MockSynapseContext.tsx.
I also did a cursory review of the rest of the files, and didn't identify any issues. Nice use of context.

@nickgros nickgros merged commit fedf323 into Sage-Bionetworks:develop Jun 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants