I've got a basic React Native app that has been barely modified and initialized using Expo:
expo init AwesomeProject
cd AwesomeProject
yarn start
The initialized app has this class:
import {
ColorSchemeName,
useColorScheme as _useColorScheme,
} from "react-native";
export default function useColorScheme(): NonNullable<ColorSchemeName> {
return _useColorScheme() as NonNullable<ColorSchemeName>;
}
I'm going through the codebase and trying to add unit tests. I've added a few rendering tests for React Native components, but here I'm trying to unit test the useColorScheme() function. Since this is a React hook, I can't call it outside of a React function body.
// Using jest
import useColorScheme from "../useColorScheme";
it('useColorScheme', () => {
const huh = useColorScheme();
});
So basically my question is - What's the best way to call a React hook using jest since the hooks can only be called from a React function? (Let it be noted that I am quite new to javascript, typescript, and React)
Related
I have a React app created with create-react-app.
I'm trying to make a custom hook using Microsoft Authentication Library (MSAL). MSAL has a custom React hook that I want to call from my own custom hook.
When I use a hook (any hook) inside my custom hook in a separate file I get this in the browser:
Warning: 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
// ourhook/index.ts
import { useEffect } from "react";
export const useMsal2 = () => {
useEffect(() => {
console.log("Hello from our hook!");
});
};
// app.tsx
import React from "react";
import { useMsal2 } from "./ourhook";
const App = () => {
useMsal2();
return <div>App</div>;
};
export default App;
If I call
const { instance } = useMsal();
directly from App.tsx everything works fine. It only appears to be a problem if my custom hook is in its own file.
From what I see I'm not violating any hook rules. I'm calling a hook that's calling a hook, and the first call is from a top level component.
I have read other threads here about hooks in hooks, but none of them has an answer that fits this problem.
Have I missed something about hook rules, or what might be causing this?
Okay, I forgot that we tried to have /ourhook as a freestanding project and then copy pasted it into a create react app app.
Some of you were right, it did have its own version of react.
I'm just going to hide under a rock for the rest of the week.
Thanks for all your help! <3
Try to add this comment just above:
import { useMsal } from "#azure/msal-react";
export const useMsal2 = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { instance } = useMsal();
const request = "";
return {
loginRedirect: () => console.log(""),
}
};
I don't know what useMsal looks like, but from what I see, you don't actually violate any hook rule.
I'm trying to make a thin wrapper to the "jsoneditor" library using a functionnal component. I'm rather new to React and worked so far mainly with hooks. So I tried to adapt the example given by the author of the library to use hooks:
https://github.com/josdejong/jsoneditor/tree/master/examples/react_demo
This is what I came up with so far:
import React, {useRef, useState, useEffect, useCallback} from 'react'
import JSONEditor from 'jsoneditor'
import styles from './JSONEditorReact.module.css'
import 'jsoneditor/dist/jsoneditor.css';
function App(){
const [json, setJson] = useState({some_key:"some_value"})
function onChangeJson(json){
setJson(json)
}
return <JSONEditorReact onChangeJson={onChangeJson} json={json}/>
}
function JSONEditorReact({onChangeJson, json}){
const containerRef = useRef()
const editorRef = useRef() // used as a namespace to have a reference to the jsoneditor object
useEffect(
() => {
console.log("mounting")
const options = {
modes: ['tree','form','view','text'],
onChangeJSON: onChangeJson
}
editorRef.current = new JSONEditor(containerRef.current, options)
return () => editorRef.current.destroy()
},
[] //eslint complains about the missing dependency "onChangeJson" here
)
useEffect(
() => {
console.log("updating")
editorRef.current.update(json)
},
[json]
)
return (
<div className={styles.container} ref={containerRef} />
)
}
export default App;
It works - but eslint complains about onChangeJson being a missing dependency in useEffect. If I add it as a dependency, useEffect runs each time the user inputs something into the json editor. This implies that the user looses focus on the editor each time he enters a character. My understanding is that when it occurs, setJson function of App is called, so App component is refreshed, causing the onChangeJson function to be-reinstanciated, so the first useEffect is rerun, and a new JSONEditor is instanciated.
I had some ideas but they don't seem satisfying:
define onChangeJson with useCallback - issue : I find it daunting to call useCallback each time I want to use my component
pass the setter function setJson of App as the "onChangeJson" property of JSONEditorReact - issue: what if I want to perform more actions than just setting the state in my callback?
Any more relevant ideas to solve the missing dependency issue without running the first useEffect on each input?
Is this a kind of use case where class components are more relevant than functional components using hooks? (the wrapper using class components looks more straightforward than mine, where I had to use a ref to create a namespace to hold my JSONEditor instance)
I want to create a Gatsby plugin with an index.js file but I want to render React components inside the plugin. I imported react:
const React = require('react');
const MyComponent = require('myComponent');
modules.exports = () => {
const myRenderedComponent = React.render(MyComponent);
...
}
But it fails because it doesn't understand the import statement I have in myComponent.js.
How can I render a component inside a Gatsby plugin instead of having to write the HTML?
I would be too silly to write the component's HTML by hand instead of just using the component itself
In my Rails project, I am using the react-rails gem, which does the following:
window.React = React;
This is pretty handy, but when I run unit tests using Jest, that global is not there and I get an error from the file containing the component I am testing saying that React is not defined.
If I define React in the component file using
import React from 'react';
Then it causes errors due to loading React twice.
How should I define a global React variable in my unit tests so they work?
In your test file, do:
import React from 'react'
describe('something',() => {
window.React = React
// so when you require() your component, window.React is already set
var MyComponent = require('MyComponent').default
it('does something', () => {
// do something
})
})
I have a react application that doesn't uses the browserify tool.
It means that the React variable is exported by the script of the react js lib called in the <head>.
// React variable is already available
var MyComponent = React.createClass({});
After implementing this component, I want to create a test for it.
I took a look at Jest documentation and I've created my component test.
/** #jsx React.DOM */
jest.dontMock('../compiled_jsx/components/my-component.js');
describe('MyComponent', function() {
it('The variables are being passed to component', function() {
var React = require('react/addons');
// In the `MyComponent` import I got the error below:
// ReferenceError: /compiled_jsx/components/my-component.js: React is not defined
var myComponent = require('../compiled_jsx/components/my-component.js');
});
In the Jest documentation example, both component and its tests uses the require function for getting the React variable.
Is there any way to expose React variable into the component?
Or it's necessary using browserify for creating this test?
Jest runs in node.js, so you need to use commonjs modules. You don't use browserify with jest. If you're really against commonjs modules you can do this assuming each file is wrapped in an iffe.
var React = typeof require === 'undefined'
? window.React
: require('react/addons');
Or alternatively as the first line of your tests, try:
global.React = require('react/addons');
And either way, export your components using:
try { module.exports = Foo; } catch (e) { window.Foo = Foo };
Personally, I don't think jest is practical if you're not using commonjs modules.