Skip to content
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 Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ publish-nightly: build
build:
parcel build packages/@react-{spectrum,aria,stately}/*/ packages/@internationalized/{message,string,date,number}/ packages/react-aria-components --no-optimize
yarn lerna run prepublishOnly
for pkg in packages/@react-{spectrum,aria,stately}/*/ packages/@internationalized/{message,string,date,number}/ packages/@adobe/react-spectrum/ packages/react-aria/ packages/react-stately/; \
for pkg in packages/@react-{spectrum,aria,stately}/*/ packages/@internationalized/{message,string,date,number}/ packages/@adobe/react-spectrum/ packages/react-aria/ packages/react-stately/ packages/react-aria-components/; \
do cp $$pkg/dist/module.js $$pkg/dist/import.mjs; \
done
sed -i.bak s/\.js/\.mjs/ packages/@react-aria/i18n/dist/import.mjs
Expand Down
1 change: 1 addition & 0 deletions packages/@react-aria/interactions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
},
"dependencies": {
"@react-aria/utils": "^3.15.0",
"@react-aria/ssr": "^3.5.0",
"@react-types/shared": "^3.17.0",
"@swc/helpers": "^0.4.14"
},
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-aria/interactions/src/useFocusVisible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import {isMac, isVirtualClick} from '@react-aria/utils';
import {useEffect, useState} from 'react';
import {useIsSSR} from '@react-aria/ssr';

export type Modality = 'keyboard' | 'pointer' | 'virtual';
type HandlerEvent = PointerEvent | MouseEvent | KeyboardEvent | FocusEvent;
Expand Down Expand Up @@ -192,7 +193,7 @@ export function useInteractionModality(): Modality {
};
}, []);

return modality;
return useIsSSR() ? null : modality;
}

/**
Expand Down
39 changes: 20 additions & 19 deletions packages/@react-aria/ssr/src/SSRProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import React, {ReactNode, useContext, useLayoutEffect, useMemo, useRef, useState
// consistent ids regardless of the loading order.
interface SSRContextValue {
prefix: string,
current: number
current: number,
isSSR: boolean
}

// Default context value to use in case there is no SSRProvider. This is fine for
Expand All @@ -33,7 +34,8 @@ interface SSRContextValue {
// SSR case multiple copies of React Aria is not supported.
const defaultContext: SSRContextValue = {
prefix: String(Math.round(Math.random() * 10000000000)),
current: 0
current: 0,
isSSR: false
};

const SSRContext = React.createContext<SSRContextValue>(defaultContext);
Expand All @@ -50,12 +52,25 @@ export interface SSRProviderProps {
export function SSRProvider(props: SSRProviderProps): JSX.Element {
let cur = useContext(SSRContext);
let counter = useCounter(cur === defaultContext);
let [isSSR, setIsSSR] = useState(true);
let value: SSRContextValue = useMemo(() => ({
// If this is the first SSRProvider, start with an empty string prefix, otherwise
// append and increment the counter.
prefix: cur === defaultContext ? '' : `${cur.prefix}-${counter}`,
current: 0
}), [cur, counter]);
current: 0,
isSSR
}), [cur, counter, isSSR]);

// If on the client, and the component was initially server rendered,
// then schedule a layout effect to update the component after hydration.
if (typeof window !== 'undefined') {
// This if statement technically breaks the rules of hooks, but is safe
// because the condition never changes after mounting.
// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
setIsSSR(false);
}, []);
}

return (
<SSRContext.Provider value={value}>
Expand Down Expand Up @@ -131,19 +146,5 @@ export function useSSRSafeId(defaultId?: string): string {
*/
export function useIsSSR(): boolean {
let cur = useContext(SSRContext);
let isInSSRContext = cur !== defaultContext;
let [isSSR, setIsSSR] = useState(isInSSRContext);

// If on the client, and the component was initially server rendered,
// then schedule a layout effect to update the component after hydration.
if (typeof window !== 'undefined' && isInSSRContext) {
// This if statement technically breaks the rules of hooks, but is safe
// because the condition never changes after mounting.
// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
setIsSSR(false);
}, []);
}

return isSSR;
return cur.isSSR;
}
13 changes: 13 additions & 0 deletions packages/@react-spectrum/combobox/test/ComboBox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {ComboBox, Item, Section} from '../';
import {Provider} from '@react-spectrum/provider';
import React from 'react';
import scaleMedium from '@adobe/spectrum-css-temp/vars/spectrum-medium-unique.css';
import {SSRProvider} from '@react-aria/ssr';
import themeLight from '@adobe/spectrum-css-temp/vars/spectrum-light-unique.css';
import {useAsyncList} from '@react-stately/data';
import {useFilter} from '@react-aria/i18n';
Expand Down Expand Up @@ -1210,6 +1211,18 @@ describe('ComboBox', function () {
expect(queryByRole('listbox')).toBeNull();
});
});

it('works with SSR', () => {
let {getByRole} = render(<SSRProvider><ExampleComboBox selectedKey="2" /></SSRProvider>);

let button = getByRole('button');
triggerPress(button);
act(() => jest.runAllTimers());

let listbox = getByRole('listbox');
let items = within(listbox).getAllByRole('option');
expect(items).toHaveLength(3);
});
});

describe('typing in the textfield', function () {
Expand Down
5 changes: 5 additions & 0 deletions packages/react-aria-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
"module": "dist/module.js",
"types": "dist/types.d.ts",
"source": "src/index.ts",
"exports": {
"types": "./dist/types.d.ts",
"import": "./dist/import.mjs",
"require": "./dist/main.js"
},
"files": [
"dist"
],
Expand Down
6 changes: 3 additions & 3 deletions packages/react-aria-components/src/Collection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {CollectionBase} from '@react-types/shared';
import {createPortal} from 'react-dom';
import {DOMProps, RenderProps} from './utils';
import {Collection as ICollection, Node, SelectionBehavior, SelectionMode, ItemProps as SharedItemProps, SectionProps as SharedSectionProps} from 'react-stately';
import {mergeProps} from 'react-aria';
import {mergeProps, useIsSSR} from 'react-aria';
import React, {cloneElement, createContext, Key, ReactElement, ReactNode, ReactPortal, useCallback, useContext, useMemo} from 'react';
import {useLayoutEffect} from '@react-aria/utils';
import {useSyncExternalStore} from 'use-sync-external-store/shim/index.js';
Expand Down Expand Up @@ -643,7 +643,7 @@ export function useCollectionChildren<T extends object>(props: CachedChildrenOpt
const ShallowRenderContext = createContext(false);

interface CollectionResult<C> {
portal: ReactPortal,
portal: ReactPortal | null,
collection: C
}

Expand All @@ -660,7 +660,7 @@ export function useCollection<T extends object, C extends BaseCollection<T>>(pro
{children}
</ShallowRenderContext.Provider>
), [children]);
let portal = createPortal(wrappedChildren, document as unknown as Element);
let portal = useIsSSR() ? null : createPortal(wrappedChildren, document as unknown as Element);

useLayoutEffect(() => {
if (document.dirtyNodes.size > 0) {
Expand Down