Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/fix-appproxy-react-router-forwardref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/shopify-app-react-router': patch
---

`AppProxyLink` now uses `forwardRef`, allowing consumers to attach a ref to the
underlying `<a>` element (e.g. `anchor.current.focus()`).
6 changes: 6 additions & 0 deletions .changeset/fix-appproxy-remix-forwardref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/shopify-app-remix': patch
---

`AppProxyForm` and `AppProxyLink` now use `forwardRef`, allowing consumers to
attach a ref to the underlying DOM element (e.g. `form.current.submit()`).
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useContext} from 'react';
import {forwardRef, useContext} from 'react';

import {AppProxyProviderContext} from '../AppProxyProvider';

Expand Down Expand Up @@ -39,20 +39,22 @@ export interface AppProxyLinkProps extends React.DetailedHTMLProps<
* }
* ```
*/
export function AppProxyLink(props: AppProxyLinkProps) {
const context = useContext(AppProxyProviderContext);
export const AppProxyLink = forwardRef<HTMLAnchorElement, AppProxyLinkProps>(
function AppProxyLink(props, ref) {
const context = useContext(AppProxyProviderContext);

if (!context) {
throw new Error(
'AppProxyLink must be used within an AppProxyProvider component',
);
}
if (!context) {
throw new Error(
'AppProxyLink must be used within an AppProxyProvider component',
);
}

const {children, href, ...otherProps} = props;
const {children, href, ...otherProps} = props;

return (
<a href={context.formatUrl(href)} {...otherProps}>
{children}
</a>
);
}
return (
<a href={context.formatUrl(href)} {...otherProps} ref={ref}>
{children}
</a>
);
},
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {createRef} from 'react';
import {mount} from '@shopify/react-testing';

import '../../../__tests__/test-helper';
Expand Down Expand Up @@ -45,4 +46,21 @@ describe('<AppProxyLink />', () => {
});
expect(component).toContainReactHtml('Hello world');
});

it('forwards ref to the underlying anchor element', () => {
// GIVEN
const ref = createRef<HTMLAnchorElement>();

// WHEN
mount(
<AppProxyProvider appUrl="http://my-app.example.io">
<AppProxyLink ref={ref} href="/my-action">
Hello world
</AppProxyLink>
</AppProxyProvider>,
);

// THEN
expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useContext} from 'react';
import {forwardRef, useContext} from 'react';
import {Form, type FormProps} from '@remix-run/react';

import {AppProxyProviderContext} from '../AppProxyProvider';
Expand Down Expand Up @@ -63,20 +63,22 @@ export interface AppProxyFormProps extends FormProps {
* }
* ```
*/
export function AppProxyForm(props: AppProxyFormProps) {
const context = useContext(AppProxyProviderContext);
export const AppProxyForm = forwardRef<HTMLFormElement, AppProxyFormProps>(
function AppProxyForm(props, ref) {
const context = useContext(AppProxyProviderContext);

if (!context) {
throw new Error(
'AppProxyForm must be used within an AppProxyProvider component',
);
}
if (!context) {
throw new Error(
'AppProxyForm must be used within an AppProxyProvider component',
);
}

const {children, action, ...otherProps} = props;
const {children, action, ...otherProps} = props;

return (
<Form action={context.formatUrl(action, false)} {...otherProps}>
{children}
</Form>
);
}
return (
<Form action={context.formatUrl(action, false)} {...otherProps} ref={ref}>
{children}
</Form>
);
},
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {createRef} from 'react';
import {mount} from '@shopify/react-testing';
import {createRemixStub} from '@remix-run/testing';

Expand Down Expand Up @@ -52,4 +53,26 @@ describe('<AppProxyForm />', () => {
expect(component).toContainReactComponent('form', {action: '/my-action/'});
expect(component).toContainReactHtml('Hello world');
});

it('forwards ref to the underlying form element', () => {
// GIVEN
const ref = createRef<HTMLFormElement>();

function MyComponent() {
return (
<AppProxyProvider appUrl="http://my-app.example.io">
<AppProxyForm ref={ref} action="/my-action">
Hello world
</AppProxyForm>
</AppProxyProvider>
);
}

// WHEN
const RemixStub = createRemixStub([{path: '/', Component: MyComponent}]);
mount(<RemixStub />);

// THEN
expect(ref.current).toBeInstanceOf(HTMLFormElement);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useContext} from 'react';
import {forwardRef, useContext} from 'react';

import {AppProxyProviderContext} from '../AppProxyProvider';

Expand Down Expand Up @@ -39,20 +39,22 @@ export interface AppProxyLinkProps extends React.DetailedHTMLProps<
* }
* ```
*/
export function AppProxyLink(props: AppProxyLinkProps) {
const context = useContext(AppProxyProviderContext);
export const AppProxyLink = forwardRef<HTMLAnchorElement, AppProxyLinkProps>(
function AppProxyLink(props, ref) {
const context = useContext(AppProxyProviderContext);

if (!context) {
throw new Error(
'AppProxyLink must be used within an AppProxyProvider component',
);
}
if (!context) {
throw new Error(
'AppProxyLink must be used within an AppProxyProvider component',
);
}

const {children, href, ...otherProps} = props;
const {children, href, ...otherProps} = props;

return (
<a href={context.formatUrl(href)} {...otherProps}>
{children}
</a>
);
}
return (
<a href={context.formatUrl(href)} {...otherProps} ref={ref}>
{children}
</a>
);
},
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {createRef} from 'react';
import {mount} from '@shopify/react-testing';

import '../../../__tests__/test-helper';
Expand Down Expand Up @@ -45,4 +46,21 @@ describe('<AppProxyLink />', () => {
});
expect(component).toContainReactHtml('Hello world');
});

it('forwards ref to the underlying anchor element', () => {
// GIVEN
const ref = createRef<HTMLAnchorElement>();

// WHEN
mount(
<AppProxyProvider appUrl="http://my-app.example.io">
<AppProxyLink ref={ref} href="/my-action">
Hello world
</AppProxyLink>
</AppProxyProvider>,
);

// THEN
expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
});
});
Loading