React hooks in post-purchase extension returns error - Invalid hook call

Topic summary

Developers encounter an “Invalid hook call” error when using React hooks in Shopify post-purchase extensions. The error message indicates potential issues with mismatched React versions, breaking Rules of Hooks, or multiple React copies in the app.

Root Cause:
The issue stems from outdated Shopify CLI versions and changes to the extension rendering API.

Solutions Identified:

  1. Update Shopify CLI (Primary fix):

    • Upgrade to version >= 3.59.2
    • Command: npm install -g @shopify/cli@latest
  2. Change render syntax:

    • Replace: render('Checkout::PostPurchase::Render', App);
    • With: render('Checkout::PostPurchase::Render', () => <App />);

Additional Context:

  • The post-purchase extension repository has moved to a new location
  • The rendering API syntax has recently changed
  • Multiple users confirmed the CLI upgrade resolves the issue
  • Developers should review updated example code in official Shopify documentation

Status: Resolved through CLI upgrade and syntax adjustment.

Summarized with AI on November 8. AI used: claude-sonnet-4-5-20250929.

Im creating a post purchase extension,

I manage to render the extension, and make the request to my server to conditionaly render it

but when i try to use any react hook inside the App function i get the error :

"Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app"

here is my code :

/**
 * Extend Shopify Checkout with a custom Post Purchase user experience.
 * This template provides two extension points:
 *
 *  1. ShouldRender - Called first, during the checkout process, when the
 *     payment page loads.
 *  2. Render - If requested by `ShouldRender`, will be rendered after checkout
 *     completes
 */
import React, { useEffect, useState } from 'react';

import {
	extend,
	render,
	BlockStack,
	Button,
	CalloutBanner,
	Heading,
	Image,
	Layout,
	TextBlock,
	TextContainer,
	View,
	useExtensionInput,
} from '@shopify/post-purchase-ui-extensions-react';

/**
 * Entry point for the `ShouldRender` Extension Point.
 *
 * Returns a value indicating whether or not to render a PostPurchase step, and
 * optionally allows data to be stored on the client for use in the `Render`
 * extension point.
 */

const APP_URL = 'myappurl.com';

extend('Checkout::PostPurchase::ShouldRender', async ({ inputData, storage }) => {
	try {
		const postPurchaseOffer = await fetch(`${APP_URL}/api/proxy/post-purchase`, {
			method: 'POST',
			headers: {
				'Authorization': `Bearer ${inputData.token}`,
				'Content-Type': 'application/json',
			},
			body: JSON.stringify({
				referenceId: inputData.initialPurchase.referenceId,
			}),
		});

		const fetchResult = await postPurchaseOffer.json();

		console.log(fetchResult.render, 'fetchResult.render');

		await storage.update(fetchResult);

		// For local development, always show the post-purchase page
		return { render: fetchResult.render };
	} catch (error) {
		console.log('Error fetching post-purchase offer:', error);
		return { render: true };
	}
});

/**
 * Entry point for the `Render` Extension Point
 *
 * Returns markup composed of remote UI components.  The Render extension can
 * optionally make use of data stored during `ShouldRender` extension point to
 * expedite time-to-first-meaningful-paint.
 */

// Top-level React component
export const App = () => {
	// const { storage, inputData, calculateChangeset, applyChangeset, done } = useExtensionInput();
	const [loading, setLoading] = useState(true);
	// const [calculatedPurchase, setCalculatedPurchase] = useState();

	// const { offers } = storage.initialData;

	return (
		<BlockStack spacing="loose">
			<CalloutBanner title="Post-purchase extension template">
				Use this template as a starting point to build a great post-purchase extension.
			</CalloutBanner>
			<Layout
				maxInlineSize={0.95}
				media={[
					{ viewportSize: 'small', sizes: [1, 30, 1] },
					{ viewportSize: 'medium', sizes: [300, 30, 0.5] },
					{ viewportSize: 'large', sizes: [400, 30, 0.33] },
				]}
			>
				<View>
					<Image source="https://cdn.shopify.com/static/images/examples/img-placeholder-1120x1120.png" />
				</View>
				<View />
				<BlockStack spacing="xloose">
					<TextContainer>
						<Heading>Post-purchase extension</Heading>
						<TextBlock>Here you can cross-sell other products, request a product review based on a previous purchase, and much more.</TextBlock>
					</TextContainer>
					<Button
						submit
						onPress={() => {
							// eslint-disable-next-line no-console
							// console.log(`Extension point ${extensionPoint}`, initialState);
							console.log('asd');
						}}
					>
						Primary button
					</Button>
				</BlockStack>
			</Layout>
		</BlockStack>
	);
};

render('Checkout::PostPurchase::Render', App);

here is my extension package.json :

{
  "name": "post-purchase-ui",
  "private": true,
  "version": "1.0.0",
  "main": "dist/main.js",
  "license": "UNLICENSED",
  "dependencies": {
    "@shopify/post-purchase-ui-extensions-react": "^0.13.2",
    "jsonwebtoken": "^9.0.2",
    "react": "^18.2.0",
    "uuid": "^9.0.1"
  },
  "devDependencies": {
    "@types/react": "^18.2.38"
  }
}

Please help :disappointed_face:

1 Like

Hi,

Did you solve this issue?

I am also having this issue on my extension.

Hey yes add me on dis i will show u: strot

We would appreciate if you could share your ideas and solution. :slightly_smiling_face:

+1 I’m experiencing the same issue after upgrading from @shopify/cli version 3.58.2 to 3.59.1

1 Like

+1, waiting for a fix

changing

render('Checkout::PostPurchase::Render', App);

to

render("Checkout::PostPurchase::Render", () =>  <App />);

solved it for me

1 Like

This alone didn’t work for me, but I tracked down the new repo for post-purchase-extension (it moved, apparently) and found a solution in the issue tracker searching for: is:issue “Invalid hook call”.

Solution: Update shopify cli to the latest version

npm i -g /cli@latest

Source: https://github.com/Shopify/ui-extensions/issues/1906#issuecomment-2097429507

I would also review the example code as it seems to have changed recently…

e.g.

render("Checkout::PostPurchase::Render", () =>

Solution is too simple:

Please upgrade your Shopify CLI in your local machine that’s it.

Required Shopify cli version >= 3.59.2

Reference: https://github.com/Shopify/ui-extensions/issues/1906#issuecomment-2097429507

and if you don’t have cli execute below command:

npm install -g /cli