I am trying to use the expo-camera for web. For reference, this is working 100% on Expo Go on my phone. But when I visit my website (hosted on AWS) which has the same build, I get a white screen after accepting camera permissions because of this 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
See https://reactjs.org/link/invalid-hook-call for tips about how to
debug and fix this problem.
I can see in chrome tools (attached to my phone) this error, but it doesn't look like anything I am doing..
react-native-logs.fx.ts:34 Error: Minified React error #321; visit https://reactjs.org/docs/error-decoder.html?invariant=321 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at V (react.production.min.js:19)
at Object.t.useRef (react.production.min.js:25)
at ExponentCamera.web.tsx:30
at na (react-dom.production.min.js:157)
at Ia (react-dom.production.min.js:176)
at Bs (react-dom.production.min.js:271)
at Sl (react-dom.production.min.js:250)
at _l (react-dom.production.min.js:250)
at wl (react-dom.production.min.js:250)
at dl (react-dom.production.min.js:243)
And this is the code it is referring to, which is of course part of expo
const ExponentCamera = React.forwardRef(
(
{ type, pictureSize, poster, ...props }: CameraNativeProps & { children?: React.ReactNode },
ref: React.Ref<ExponentCameraRef>
) => {
const video = React.useRef<HTMLVideoElement | null>(null);
const native = useWebCameraStream(video, type as CameraType, props, {
onCameraReady() {
if (props.onCameraReady) {
props.onCameraReady();
}
},
onMountError: props.onMountError,
});
Here is my code
import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { NavigationProp, ParamListBase } from '#react-navigation/native';
import { BarCodeScanningResult, Camera, PermissionResponse, PermissionStatus } from 'expo-camera';
export interface BarCodeScannerScreenProps {
navigation: NavigationProp<ParamListBase>
}
export const BarCodeScannerScreen = (props: BarCodeScannerScreenProps) => {
const [cameraPermission, setCameraPermission] = React.useState<string>(PermissionStatus.UNDETERMINED);
const getPermission = async () => {
const response: PermissionResponse = await Camera.requestCameraPermissionsAsync();
setCameraPermission(response.status);
};
const onBarCodeScanned = (event: BarCodeScanningResult) => {
props.navigation.navigate("BookSearch", {searchValue: event.data});
};
React.useEffect(() => {
getPermission();
}, []);
if(cameraPermission === PermissionStatus.UNDETERMINED){
return <Text>Requesting camera permission</Text>;
}
else if(cameraPermission === PermissionStatus.GRANTED){
return (
<Camera
onBarCodeScanned={onBarCodeScanned}
style={StyleSheet.absoluteFill}
/>
);
}
else {
return <Text>No access to camera</Text>;
}
}
Maybe I do have some invalid hook or something in my code? But I don't see why it would work fine on Expo Go.
The recipe from a React issue comment worked for me:
expo customize:web
select webpack (PRESS SPACE)
enter
Then modify webpack.config.js file this way:
const createExpoWebpackConfigAsync = require('#expo/webpack-config');
const path = require('path');
module.exports = async function (env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv);
// Customize the config before returning it.
config.resolve.alias = {
react: path.resolve('./node_modules/react')
}
return config;
};
Related
i18n support is not compatible with next export.
NextJS dont run the deploy with i18n
Im using nextJS 10, and the main reason that i choose next 10, is that i can do SSR and use the i18n.
Internationalized Routing its a new next js 10 feature and have a page only to tha feature.
But when im gonna do a deploy, this error appears: i18n support is not compatible with next export.
Theres nothing about this in internationalized routing page.
My code
next.config.js
const withImages = require('next-images')
const path = require('path')
module.exports = withImages({
esModule: false,
i18n: {
locales: ['en-US', 'pt-BR', 'pt-PT', 'es-ES'],
defaultLocale: 'pt-BR',
},
});
I created a translate archive that make the condition with next router
obs: PT and EN are JSON files with text
import * as pt from "./pt";
import * as en from './en';
import { useRouter } from "next/router"
export const traducao = () =>{
let routes = useRouter();
let translate;
if (routes.locale == 'pt-PT' || routes.locale == 'pt-BR') {
translate = pt.default;
} else {
translate = en.default;
}
return translate
}
And the i just use in my project like a function:
{traducao().homeText.button_text}
Work well, recognizes the browser language and switch.
Im using deploy script
npm run deploy
"deploy": "npm run clean && npm run build && next export -o dist/"
Steps to reproduce
Go to 'next.config,js'
create the i18n export
create a Translate file that recognizes the browser language
import JSONs files with your site text
Use where you want
Try to deploy
Expected behavior
Its just suppose to work fine and Deploy normal.
Screenshots
System information
OS: Linux Ubuntu
IDE: VSCode
Version of Next.js: 10
Version of Node.js: v15.3.0
Deployment: next deploy
Digging through issues on vercel's github I found this alternative that doesn't use next-i18next or any other nextjs magic:
https://github.com/Xairoo/nextjs-i18n-static-page-starter
It's just basic i18n using i18next that bundles all locale together with JS, so there are obvious tradeoffs but at least it works with SSG. You can build upon that to come up with something more elaborate. That's what I will do.
You can't use export with next.js i18n implementation.
Note that Internationalized Routing does not integrate with next export as next export does not leverage the Next.js routing layer. Hybrid Next.js applications that do not use next export are fully supported.
Next.js docs
There's an alternative, by not using the i18n feature of next.js completely and creating the i18n language detection yourself.
An example that uses the next-language-detector module is described in this blog post and may look like this:
// languageDetector.js
import languageDetector from 'next-language-detector'
import i18nextConfig from '../next-i18next.config'
export default languageDetector({
supportedLngs: i18nextConfig.i18n.locales,
fallbackLng: i18nextConfig.i18n.defaultLocale
})
// redirect.js
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import languageDetector from './languageDetector'
export const useRedirect = (to) => {
const router = useRouter()
to = to || router.asPath
// language detection
useEffect(() => {
const detectedLng = languageDetector.detect()
if (to.startsWith('/' + detectedLng) && router.route === '/404') { // prevent endless loop
router.replace('/' + detectedLng + router.route)
return
}
languageDetector.cache(detectedLng)
router.replace('/' + detectedLng + to)
})
return <></>
};
export const Redirect = () => {
useRedirect()
return <></>
}
// eslint-disable-next-line react/display-name
export const getRedirect = (to) => () => {
useRedirect(to)
return <></>
}
The complete guide and the example code can be found here:
guide
example
Hello I show you my soluce with only i18n-js
// i18n.ts
import i18n from "i18n-js";
import en from "./en.json";
import fr from "./fr.json";
const localeEnable = ["fr", "en"];
const formatLocale = () => {
const { language } = window.navigator;
if (language.includes("en")) return "en";
if (language.includes("fr")) return "fr";
if (!localeEnable.includes(language)) return "en";
return "en";
};
// Set the key-value pairs for the different languages you want to support.
i18n.translations = {
en,
fr,
};
// Set the locale once at the beginning of your app.
i18n.locale = "en";
const useTranslate = () => {
return (t: string) => {
if (typeof window !== "undefined") {
i18n.locale = formatLocale();
}
return i18n.t(t);
};
};
export default useTranslate;
// home.tsx
import useTranslate from "../locales/i18n";
const t = useTranslate();
return (<p>{t("idstring")}</p>)
Trying to create an xterm react component in Next.js I got stuck as I'm not able to get over an error message I've never got before.
I'm trying to import a npm client-side module called xterm, but if I add the import line the application crashes.
import { Terminal } from 'xterm'
The error reads Server Error... ReferenceError: self is not defined
and then shows this chunk of code as Source
module.exports = require("xterm");
According to some research I did, this has to do with Webpack and could be helped if something like this was done:
output: {
globalObject: 'this'
}
Would you know how to fix this?
The error occurs because the library requires Web APIs to work, which are not available when Next.js pre-renders the page on the server-side.
In your case, xterm tries to access the window object which is not present on the server. To fix it, you have to dynamically import xterm so it only gets loaded on the client-side.
There are a couple of ways to achieve this in Next.js.
#1 Using dynamic import()
Move the import to your component's useEffect, then dynamically import the library and add your logic there.
useEffect(() => {
const initTerminal = async () => {
const { Terminal } = await import('xterm')
const term = new Terminal()
// Add logic with `term`
}
initTerminal()
}, [])
#2 Using next/dynamic with ssr: false
Create a component where you add the xterm logic.
// components/terminal-component
import { Terminal } from 'xterm'
function TerminalComponent() {
const term = new Terminal()
// Add logic around `term`
return <></>
}
export default TerminalComponent
Then dynamically import that component when using it.
import dynamic from 'next/dynamic'
const TerminalComponent = dynamic(() => import('<path-to>/components/terminal-component'), {
ssr: false
})
As an alternative, you could add the logic directly when dynamically importing the library with next/dynamic to avoid having an extra file for it.
import dynamic from 'next/dynamic'
const Terminal = dynamic(
{
loader: () => import('xterm').then((mod) => mod.Terminal),
render: (props, Terminal) => {
const term = new Terminal()
// Add logic with `term`
return <></>
}
},
{
ssr: false
}
)
I have an app.js with the following const function:
const serverUrl = process.env.REACT_APP_SERVER_URL;
const { getAccessTokenSilently } = useAuth0();
const callEndpoint = async (props) => {
const {endpoint} = props;
const token = await getAccessTokenSilently();
try {
const token = await getAccessTokenSilently();
console.log('endpoint is:', `${serverUrl}/${props.endpoint}`);
const response = await fetch(
`${serverUrl}/${props.endpoint}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const responseData = await response.json();
console.log(responseData.message);
} catch (error) {
console.log(error.message);
}
};
Further down in the render:
<Button variant="outline-primary" onClick = { (e) => {callEndpoint({endpoint:"get_timestamp_public"})}} >Fetch Public Data</Button>
It works fine.
However, when I try to move my endpoint into a new js file called 'api_funcs.js':
import React, { useEffect, useState } from 'react';
import { useAuth0 } from '#auth0/auth0-react';
const UseAPI = (props) => {
const {endpoint} = props;
const serverUrl = process.env.REACT_APP_SERVER_URL;
console.log("I am in here");
const { getAccessTokenSilently } = useAuth0();
const callEndpoint = async () => {
const token = await getAccessTokenSilently();
try {
// const token = await getAccessTokenSilently();
const response = await fetch(
`${serverUrl}/{props.endpoint}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const responseData = await response.json();
//setMessage(responseData.message);
console.log(responseData.message);
} catch (error) {
//setMessage(error.message);
console.log(error.message);
}
};
}
export default UseAPI;
The const { getAccessTokenSilently } = useAuth0(); throws me an error of:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
I don't understand -- isn't my UseAPI a function component? What am I missing?
As you are not breaking the Rules of Hooks.
There might be two common reasons because of which you might be seeing this error:
You might have mismatching versions of React and React DOM.
You might be using a version of react-dom (< 16.8.0) that doesn’t yet support Hooks. You can run
> npm ls react-dom
in your application folder to check which version you’re using.
You might have more than one copy of React in the same app.
In order for Hooks to work, the react import from your application code needs to resolve to the same module as the react import from inside the react-dom package.
If these react imports resolve to two different exports objects, you will see this warning. This may happen if you accidentally end up with two copies of the react package.
If you use Node for package management, you can run this check-in your project folder:
npm ls react
If you see more than one React, you’ll need to figure out why this happens and fix your dependency tree. For example, maybe a library you’re using incorrectly specifies react as a dependency (rather than a peer dependency). Until that library is fixed, Yarn resolutions is one possible workaround.
You can also try to debug this problem by adding some logs and restarting your development server:
// Add this in node_modules/react-dom/index.js window.React1 =
require('react');
// Add this in your component file require('react-dom'); window.React2
= require('react'); console.log(window.React1 === window.React2);
If it prints false then you might have two Reacts and need to figure out why that happened. This issue includes some common reasons encountered by the community.
This problem can also come up when you use npm link or an equivalent. In that case, your bundler might “see” two Reacts — one in application folder and one in your library folder. Assuming myapp and mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib. This should make the library use the application’s React copy.
Thus, running the command
npm link <path_to_local_library>/node_modules/react
might solve your issue.
Note
In general, React supports using multiple independent copies on one page (for example, if an app and a third-party widget both use it). It only breaks if require('react') resolves differently between the component and the react-dom copy it was rendered with.
Source React Doc
I do use recoil in my nextjs application.
But if I run next (in dev or production make no difference) I got this error-message:
Duplicate atom key "companyData". This is a FATAL ERROR in
production. But it is safe to ignore this warning if it occurred because of
hot module replacement.
This is the way I've implemented it:
/src/stores/CompanyStore.js:
import { useSetRecoilState, useRecoilValue , atom } from 'recoil';
import config from '../../content/config.yml';
const companyData = atom({
key: 'companyData',
default: {...config.company},
});
export const useSetCompanyData = () => useSetRecoilState(companyData);
export const useCompanyData = () => useRecoilValue(companyData);
export default {
useSetCompanyData,
useCompanyData,
};
I use it like this in some components:
MyComponent.js
import React from 'react';
...
...
import {useCompanyData} from '../stores/CompanyStore';
const MyComponent = () => {
const classes = useStyles();
const companyData = useCompanyData();
const { summary: headline, description } = companyData;
return (<div><h2>{headline}</h2><p>{description}</p>)
I don't see, why this error-message appears. Might it caused of a bug in nextjs, or did I implement recoil in a wrong way?
Looks like a problem with recoil in nextjs when you have state in a separate file:
https://github.com/facebookexperimental/Recoil/issues/733
As of Recoil 0.7.6, add RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false to your environment to hide these warnings.
(roeland had the correct GitHub issue, which has since been closed)
This is probably more of a JavaScript/TypeScript question then it is about React/Testing.
But I'll give the complete story. So I have a test app with basic routing functionality and tests to verify that the routing works.
App.tsx
https://github.com/Leejjon/pwa-seo/blob/6f621968de1184b03744a262a68d291b4571c5c1/src/App.tsx
App.test.tsx
https://github.com/Leejjon/pwa-seo/blob/6f621968de1184b03744a262a68d291b4571c5c1/src/App.test.tsx
Everything worked fine. Then I added an useEffect hook to initialize my internationalization library:
useEffect(() => {
async function initMessages() {
await intl.init({
currentLocale: "en-US",
locales
});
}
initMessages().then(() => setLoading(false));
}, [loading]);
This loads all my text assets in English. This works fine, but broke all my tests with the following error message:
Warning: An update to App inside a test was not wrapped in act(...).
After some reading up on the internet I managed to fix my tests by adding this 'act' function, here is one example:
import React from 'react';
import {act, render, fireEvent, waitForElement} from '#testing-library/react';
import "#testing-library/jest-dom/extend-expect";
import App from './App';
test('Verify home page header', async() => {
let app: HTMLElement;
await act(async () => {
const {container} = render(<App/>);
app = container;
});
// #ts-ignore
if (app) {
const pageHeaderContent = app.querySelector("#pageHeader")?.firstChild?.textContent;
expect(pageHeaderContent).toMatch('Home page');
} else {
fail("The app should have been initialized.");
}
});
Right now I'm suppressing the TS2454: Variable 'app' is used before being assigned. warning with the #ts-ignore. This is ugly. If I move my assertions into the act function, I get the same Warning: An update to App inside a test was not wrapped in act(...). error again.
Is there a way to obtain the container object destructured from the render function without having to use the #ts-ignore and the if clause to do null checking?
I created a tag for the current code related to this question:
https://github.com/Leejjon/pwa-seo/releases/tag/uglylines
Link to last commit: https://github.com/Leejjon/pwa-seo/commit/2434f78c0619be2d55f9de965149f6bd6d1a0b90
Typescript is complaining about the app variable to not have been initialised when you access it in the if-statement. You can simply fix that by assigning null to it.
let app: HTMLElement = null;
In case you use strict null checks you have to allow null on the type:
let app: HTMLElement | null = null;
After puzzling this is my result
test('Verify home page header', async() => {
let app: HTMLElement | undefined = undefined;
await act(async () => {
const {container} = render(<App/>);
app = container;
});
let appAsHtmlElement = (app as unknown as HTMLElement);
const pageHeaderContent = appAsHtmlElement.querySelector("#pageHeader")?.firstChild?.textContent;
expect(pageHeaderContent).toMatch('Home page');
});
Better suggestions (if there is some way of not having to use the 'act' function) are still welcome.