preactjs / preact

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.
https://preactjs.com
MIT License
36.71k stars 1.95k forks source link

Error: Hook can only be invoked from render methods. #3308

Closed tw1t611 closed 2 years ago

tw1t611 commented 2 years ago

Describe the bug I upgraded to nextjs v12 and got this error: Error: Hook can only be invoked from render methods. image The error gets thrown on every other react hook as well.

To Reproduce

store.js

import { createContext } from "preact";
import { useReducer, useContext } from "preact/hooks";

const reducer = (state, action) => {
  switch (action.type) {
    case "filter":
      return { ...state, filter: action.data };
    default:
      return;
  }
};

const initialState = {
  filter: { category: null },
};
const StoreContext = createContext(initialState);

export function StoreProvider(props) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {props.children}
    </StoreContext.Provider>
  );
}

export function useStore() {
  const store = useContext(StoreContext);

  return store;
}

Steps to reproduce the behavior:

  1. Add store.js to a nextjs v12 project
  2. See error

Expected behavior The code should just work as before.

Additional Info package.json

{
  "private": true,
  "license": "The Unlicense",
  "scripts": {
    "dev": "npx netlify-cms-proxy-server & next dev",
    "build": "next build",
    "start": "next start",
    "postbuild": "next-sitemap"
  },
  "dependencies": {
    "framer-motion": "^5.2.1",
    "next": "^12.0.3",
    "next-mdx-remote": "^3.0.7",
    "next-plugin-preact": "^3.0.6",
    "next-pwa": "^5.4.0",
    "preact": "^10.5.14",
    "preact-render-to-string": "^5.1.19",
    "react": "npm:@preact/compat",
    "react-dom": "npm:@preact/compat",
    "react-intersection-observer": "^8.32.2",
    "react-lazy-hydration": "^0.1.0",
    "react-responsive": "^9.0.0-beta.4",
    "react-ssr-prepass": "npm:preact-ssr-prepass"
  },
  "devDependencies": {
    "@tailwindcss/forms": "^0.4.0-alpha.1",
    "@tailwindcss/typography": "^0.5.0-alpha.2",
    "autoprefixer": "^10.4.0",
    "eslint": "<8.0.0",
    "eslint-config-next": "12.0.3",
    "gray-matter": "^4.0.3",
    "netlify-cms-proxy-server": "^1.3.22",
    "next-sitemap": "^1.6.203",
    "postcss": "^8.3.11",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-preset-env": "^6.7.0",
    "sharp": "^0.29.2",
    "tailwindcss": "^3.0.0-alpha.1"
  }
node --version
v16.11.0
tw1t611 commented 2 years ago

Hey, could you give an update please about what the problem is and if you already had time to work on it?

JoviDeCroock commented 2 years ago

Well, I've been debugging a similar issue on Next. Adding getServerSideProps to the page made it work which showed the issue that Next has stopped deduping dependencies when it attempts to statically prerender. https://github.com/vercel/next.js/issues/31538

tw1t611 commented 2 years ago

Thank you very much. Guess I can close this now and follow the nextjs issue. :)

JoviDeCroock commented 2 years ago

Oh feel free to keep it open, you can test whether this applies to you as well by adding getServerSideProps

tw1t611 commented 2 years ago

Hey, just upgraded to next 12 again. Seems like there is a problem with the compat exports now:

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './compat/package.json' is not defined by "exports" in /home/user/Projects/bestnotebook-tech/node_modules/preact/package.json
    at new NodeError (node:internal/errors:371:5)
    at throwExportsNotFound (node:internal/modules/esm/resolve:440:9)
    at packageExportsResolve (node:internal/modules/esm/resolve:692:3)
    at resolveExports (node:internal/modules/cjs/loader:482:36)
    at Function.Module._findPath (node:internal/modules/cjs/loader:522:31)
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:919:27)
    at Function.mod._resolveFilename (/home/user/Projects/bestnotebook-tech/node_modules/next/dist/build/webpack/require-hook.js:171:28)
    at Function.Module._resolveFilename (/home/user/Projects/bestnotebook-tech/node_modules/module-alias/index.js:49:29)
    at Function.resolve (node:internal/modules/cjs/helpers:108:19)
    at getPackagePath (/home/user/Projects/bestnotebook-tech/node_modules/next/dist/build/webpack-config.js:543:41) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}
error Command failed with exit code 1.

package.json

{
  "private": true,
  "license": "The Unlicense",
  "scripts": {
    "dev": "npx netlify-cms-proxy-server & next dev",
    "build": "next build",
    "start": "next start",
    "postbuild": "next-sitemap"
  },
  "dependencies": {
    "framer-motion": "^5.3.3",
    "next": "^12.0.4",
    "next-mdx-remote": "^3.0.7",
    "next-plugin-preact": "^3.0.6",
    "next-pwa": "^5.4.1",
    "preact": "^10.6.1",
    "preact-render-to-string": "^5.1.19",
    "react": "npm:@preact/compat",
    "react-dom": "npm:@preact/compat",
    "react-intersection-observer": "^8.32.5",
    "react-lazy-hydration": "^0.1.0",
    "react-responsive": "^9.0.0-beta.5",
    "react-ssr-prepass": "npm:preact-ssr-prepass"
  },
  "devDependencies": {
    "@tailwindcss/forms": "^0.4.0-alpha.1",
    "@tailwindcss/typography": "^0.5.0-alpha.2",
    "autoprefixer": "^10.4.0",
    "eslint": "8.3.0",
    "eslint-config-next": "12.0.4",
    "gray-matter": "^4.0.3",
    "netlify-cms-proxy-server": "^1.3.22",
    "next-sitemap": "^1.6.203",
    "postcss": "^8.4.3",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-preset-env": "^7.0.1",
    "sharp": "^0.29.2",
    "tailwindcss": "^3.0.0-alpha.1"
  }
}
JoviDeCroock commented 2 years ago

Will be in the next release

JoviDeCroock commented 2 years ago

@tw1t611 this has been published in 10.6.2

JoviDeCroock commented 2 years ago

I'll close this for now, feel free to comment in case there are more issues

tw1t611 commented 2 years ago

Hey, came up again. :(

package.json

{
  "private": true,
  "license": "The Unlicense",
  "scripts": {
    "dev": "npx netlify-cms-proxy-server & next dev",
    "build": "next build",
    "start": "next start",
    "postbuild": "next-sitemap"
  },
  "dependencies": {
    "framer-motion": "^5.3.3",
    "next": "^12.0.5",
    "next-mdx-remote": "^3.0.7",
    "next-plugin-preact": "^3.0.6",
    "next-pwa": "^5.4.1",
    "preact": "^10.6.2",
    "preact-render-to-string": "^5.1.19",
    "react": "npm:@preact/compat",
    "react-dom": "npm:@preact/compat",
    "react-intersection-observer": "^8.32.5",
    "react-lazy-hydration": "^0.1.0",
    "react-responsive": "^9.0.0-beta.5",
    "react-ssr-prepass": "npm:preact-ssr-prepass",
    "scheduler": "^0.20.2"
  },
  "devDependencies": {
    "@tailwindcss/forms": "^0.4.0-alpha.1",
    "@tailwindcss/typography": "^0.5.0-alpha.2",
    "autoprefixer": "^10.4.0",
    "eslint": "8.4.1",
    "eslint-config-next": "12.0.7",
    "gray-matter": "^4.0.3",
    "netlify-cms-proxy-server": "^1.3.22",
    "next-sitemap": "^1.6.203",
    "postcss": "^8.4.3",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-preset-env": "^7.0.1",
    "sharp": "^0.29.2",
    "tailwindcss": "^3.0.0-alpha.1"
  }
}
tw1t611 commented 2 years ago

@JoviDeCroock Hey, I am a little late, but I checked the issue again and it still isn't working for me.

Running yarn upgrade --latest got me the newest version (10.6.4). After running yarn dev, the first error occured again.

Error: Cannot find module 'scheduler/package.json'

After installing the scheduler package, the error described above came up again.

Do you need more info for reproduction?

{
  "private": true,
  "license": "The Unlicense",
  "scripts": {
    "dev": "npx netlify-cms-proxy-server & next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "postbuild": "next-sitemap"
  },
  "dependencies": {
    "amazon-product-api": "^0.4.4",
    "framer-motion": "^5.5.6",
    "next": "^12.0.7",
    "next-mdx-remote": "^3.0.7",
    "next-plugin-preact": "^3.0.6",
    "next-pwa": "^5.4.1",
    "preact": "^10.6.4",
    "preact-render-to-string": "^5.1.19",
    "react": "npm:@preact/compat",
    "react-dom": "npm:@preact/compat",
    "react-intersection-observer": "^8.32.5",
    "react-lazy-hydration": "^0.1.0",
    "react-responsive": "^9.0.0-beta.5",
    "react-ssr-prepass": "npm:preact-ssr-prepass",
    "tailwindcss-elevation": "^1.0.1"
  },
  "devDependencies": {
    "@tailwindcss/forms": "^0.4.0-alpha.1",
    "@tailwindcss/typography": "^0.5.0-alpha.2",
    "autoprefixer": "^10.4.2",
    "eslint": "^8.6.0",
    "eslint-config-next": "12.0.7",
    "gray-matter": "^4.0.3",
    "netlify-cms-proxy-server": "^1.3.23",
    "next-sitemap": "^1.6.245",
    "postcss": "^8.4.5",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-preset-env": "^7.2.0",
    "sharp": "^0.29.2",
    "tailwindcss": "^3.0.12"
  }
}
herrKlein commented 2 years ago

got the same error when adding unistore to a preact project

benzizoo commented 2 years ago

happens to me as well while using react-hook-form and Next js ^v12.x

teodragovic commented 2 years ago

Happens to me as well. Here is minimal repro (using create-next-app)

// package.js
  "dependencies": {
    "next": "12.0.8",
    "next-plugin-preact": "^3.0.6",
    "preact": "^10.6.4",
    "preact-render-to-string": "^5.1.19",
    "react-ssr-prepass": "npm:preact-ssr-prepass@^1.2.0",
    "react": "npm:@preact/compat@^17.0.3",
    "react-dom": "npm:@preact/compat@^17.0.3",
    "react-redux": "^7.2.6",
    "redux": "^4.1.2"
  },
  "devDependencies": {
    "@types/node": "17.0.8",
    "@types/react": "17.0.38",
    "eslint": "8.6.0",
    "eslint-config-next": "12.0.8",
    "scheduler": "^0.20.2",
    "typescript": "4.5.4"
  }
// src/store.js
import { createStore, combineReducers } from 'redux';

const appReducer = combineReducers({});

const reducer = (state, action) => {
    return appReducer(state, action);
};

const store = createStore(reducer, {});

export default store;
// src/pages/_app.js

import { h } from 'preact';
import { Provider } from 'react-redux';

import store from '../store';

const MyApp = ({ Component, pageProps }) => {

    const getLayout = Component.getLayout || ((page) => page);
    return (
        <Provider store={ store }>
            { getLayout(<Component { ...pageProps } />) }
        </Provider>
    );
};

export default MyApp;
// src/pages/index.js
import { h } from 'preact';
import { useState } from 'preact/hooks';

const Login = () => {
    const [ counter ] = useState(1);
    return (
        <div>hello {couter}</div>
    );
};
const LoginLayout = ({ children }) => <article>{ children }</article>

export default Login;

Login.getLayout = function getLayout(page) {
    return (
        <LoginLayout>
            { page }
        </LoginLayout>
    );
};
teodragovic commented 2 years ago

FWIW I bumped down next to v11.1.3 and error is gone

scheatkode commented 2 years ago

As a workaround for anyone bumping into this issue, try importing from react instead of preact/hooks.

import { useReducer, useState } from 'react'

Hoping this helps.

acolle commented 2 years ago

Has anyone found a clear fix for this issue?

I tried @scheatkode solution but the preact/debug package stills shows the Uncaught Error: Hook can only be invoked from render methods.

I'm trying to build and deploy my Preact app via Docker with Nginx.

// package.json
"scripts": {
    "build": "preact build",
    "np-build": "preact build --no-prerender",
    "serve": "sirv build --port 8080 --cors --single",
    "dev": "preact watch",
    "lint": "eslint src",
    "test": "jest"
  },
  "eslintConfig": {
    "extends": "preact",
    "ignorePatterns": [
      "build/"
    ]
  },
  "devDependencies": {
    "autoprefixer": "^9.8.8",
    "enzyme": "^3.10.0",
    "enzyme-adapter-preact-pure": "^2.0.0",
    "eslint": "^7.5.1",
    "eslint-config-preact": "^1.1.0",
    "jest": "^27.3.1",
    "jest-preset-preact": "^4.0.5",
    "postcss": "^7.0.39",
    "sirv-cli": "1.0.3",
    "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"
  },
  "dependencies": {
    "history": "^5.0.1",
    "preact": "^10.3.2",
    "preact-cli": "^0.1.0",
    "preact-render-to-string": "^5.1.4",
    "preact-router": "^3.2.1",
    "react": "^18.2.0",
    "webpack": "^4.46.0"
  },
  "jest": {
    "preset": "jest-preset-preact",
    "setupFiles": [
      "<rootDir>/tests/__mocks__/browserMocks.js",
      "<rootDir>/tests/__mocks__/setupTests.js"
    ]
  }

And my useReducer hook

// AuthProvider.js

import { h } from 'preact';
//import { useEffect, useReducer, useState } from 'preact/hooks';
import { useEffect, useReducer, useState } from 'react';
import { route } from 'preact-router';

import AuthContext from '../context/AuthContext';
import authReducer from '../reducers/auth';
import { activeSession, startLogout } from '../actions/authentication';
import { getCurrentUser } from '../actions/users';
import { getLocalStorage, checkValidSession } from '../utils/localStorage';

const AuthProvider = (props) => {

  const [ state, dispatch ] = useReducer(authReducer, {});
  const [ stateReady, setStateReady ] = useState(false);

  const checkActiveSession = async () => {

    if (!state.isLoggedIn) {
      let token = state.token;
      if (!token) {
        const localToken = getLocalStorage('token');
        if (localToken) {
          token = localToken;
        }
      }

      if (token) {
        try {
          const result = await getCurrentUser(token);
          if (result.type === 'ACTIVE') {
            dispatch(result);
          } else {
            logout();
          }
        } catch (err) {
          logout();
        }
      } else {
        logout();
      }
    }

    setStateReady(true);
  }

  const logout = async () => {
    const result = await startLogout();
    if (result.type === 'LOGOUT') {
      dispatch(result);
      route('/auth', true);
    }
  }

  useEffect(() => {
    checkActiveSession();
  }, []);

  return (
    stateReady && (
      <AuthContext.Provider value={{ state, dispatch }}>
        {props.children}
      </AuthContext.Provider>
    )
  )
}

export default AuthProvider;
scheatkode commented 2 years ago

Hey @acolle, the workaround works because of preact/compat being linked to react. Just install the following packages to make it work react@npm:@preact/compat and react-dom@npm:@preact/compat.

Hoping this helps

acolle commented 2 years ago

Thanks for the clarification @scheatkode. I installed the react@npm:@preact/compat and react-dom@npm:@preact/compat packages but the issue remained for me.

In the end, I got it working by installing the latest versions of preact and preact-cli. I must have fallen a bit behind regarding the latest releases.

Thanks for your quick reply

Ethaan commented 2 years ago

vercel/next.js#31538

Did you find a solution?

uxtechie commented 2 years ago

maybe this is a general workaround?

example error: ERR! Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './package.json' is not defined by "exports" in node_modules/.pnpm/@storybook+ui@6.5.9_dmmjpkrptnkrz2kzqikv5vt4mi/node_modules/react/package.json

solved:

nano node_modules/.pnpm/@storybook+ui@6.5.9_dmmjpkrptnkrz2kzqikv5vt4mi/node_modules/react/package.json

just add "./package.json": "./package.json" to module exports declaration

{
  "public": true,
  "name": "@preact/compat",
  "version": "17.1.1",
  "description": "Alias of preact/compat",
  "main": "./index.js",
  "module": "./index.mjs",
  "exports": {
    ".": {
      "module": "./index.mjs",
      "import": "./index.mjs",
      "require": "./index.js"
    },
    "./server": {
      "module": "./server.mjs",
      "import": "./server.mjs",
      "require": "./server.js"
    },
    "./server.browser": {
      "module": "./server.mjs",
      "import": "./server.mjs",
      "require": "./server.js"
    },
    "./jsx-dev-runtime": {
      "module": "./jsx-dev-runtime.mjs",
      "import": "./jsx-dev-runtime.mjs",
      "require": "./jsx-dev-runtime.js"
    },
    "./jsx-runtime": {
      "module": "./jsx-runtime.mjs",
      "import": "./jsx-runtime.mjs",
      "require": "./jsx-runtime.js"
    },
    "./package.json": "./package.json"
  },
  "repository": "preactjs/compat-alias-package",
  "author": "Preact Team <team@preactjs.com>",
  "license": "MIT",
  "homepage": "https://preactjs.com",
  "peerDependencies": {
    "preact": "*"
  },
  "devDependencies": {
    "preact": "^10.5.15"
  }
}

My question: is a good idea add this to the official react/compat package or is a mistaken config?

drager commented 2 years ago

Any news about this? Just created a new next.js project with latest preact and I get this error :(

EDIT: Works fine if I add experimental: {esmExternals: false} to my next.config.js

lukenems commented 1 year ago

using Preact@10.11.2 + Vite@3.2.0

I'm getting this same error when trying to upload multiple images to firebase storage, everything was working fine: I added state to a custom hook for holding return value from getDownloadURL(). Some code for reference:

import { useState } from "preact/hooks";
import { storage } from '../firebase';
import { getDownloadURL, ref, uploadBytes } from 'firebase/storage';

export const useUploadImages =  (files, name) => {
  const [imagePath, setImagePath] = useState([]);
  const images = [...files];

  images.forEach(image => {
    const imageRef = ref(storage, `Client_Images/${name}/${image.name}`);
    uploadBytes(imageRef, files)
      .then((snapshot) => {
        getDownloadURL(snapshot.ref)
          .then((url) => {
            setImagePath((prev) => [...prev, url]);
          })
        })
      })
  return imagePath;
}
rhengles commented 1 year ago

I'm having this problem as described here: https://github.com/preactjs/preact-ssr-prepass/issues/48 It appears to only happen on SSR. Maybe comp only exists on the client ? @JoviDeCroock

// preact/debug/src/debug.mjs line 255
    options._hook = (comp, index, type) => {
        if (!comp || !hooksAllowed) {
            throw new Error('Hook can only be invoked from render methods.');
        }

        if (oldHook) oldHook(comp, index, type);
    };