I encounter the following error:
Error: FIRESTORE (9.6.2) INTERNAL ASSERTION FAILED: Unexpected state
while trying to test my React component:
export default function ImageList() {
const [imagesUrl, setImagesUrl] = useState([]);
useEffect( () => {
const unsubscribe = getImages(setImagesUrl);
return () => unsubscribe();
}, []);
return (
// JSX
)
}
getImages is a function that subscribes to Firebase Firestore with onSnapShot function:
import { collection, onSnapshot from "firebase/firestore";
import { database } from "./setup";
const getImages = ( handleResponse ) => {
const unsubscribe = onSnapshot( collection(database, "/images"), snapshot => {
const _imagesUrl = [];
snapshot.forEach((doc) => {
const data = doc.data();
_imagesUrl.push(data.imageUrl);
});
handleResponse(_imagesUrl);
} )
return unsubscribe;
}
export default getImages;
Below is my test component:
import { render, screen } from '#testing-library/react';
import ImageList from "./ImageList";
test("list of images exist", async () => {
render( <ImageList /> );
const imgElement = await screen.findByRole("img");
expect(imgElement).toBeInTheDocument();
})
I have followed this Stack Overflow answer to isolate my React component with Firebase logic so that I can test each of them separately with React Testing Library and Jest, but it does not seem to work in my case.
Do you know I can resolve this issue?
Related
What I want to do
We have created hooks that communicate useMutation and switch the display when a button is clicked in a React component.
This test code is created using jest.
Occurring problems
Error: Uncaught [TypeError: Cannot read properties of undefined (reading '0')]
Source code in question
# Todo.jsx
import React, { useState } from 'react';
export const Todo = () => {
const [token, setToken] = useState('')
const [mutateFunction] = useMutation(CREATE_TOKEN);
const changeTodo = async (agreement) => {
const createTokenData = await mutateFunction();
if (createTokenData.data?.token === null) {
setToken('')
return;
}
setToken(createTokenData.data?.token)
}
};
return (
<div>
<button onClick={() => changeTodo(true)}>
Change Todo
</button>
</div>
)
};
# Todo.test.jsx
import React from 'react';
import { MockedProvider } from '#apollo/client/testing';
import { render, screen, fireEvent } from '#testing-library/react';
import { Todo } from 'Todo'
jest.mock('#apollo/client');
describe('Click the button', () => {
test('The value of token is null', async () => {
const mocks = [
{
request: {
query: CREATE_TOKEN,
},
result: {
data: {
createPcfToken: null
},
},
},
];
render(
<MockedProvider mocks={mocks} addTypename={false}>
<Todo />
</MockedProvider>
);
const button = screen.getByRole('button');
fireEvent.click(button);
});
});
What we tried
I am creating it while referring to the apollo official website, but I cannot receive data from mutateFunction.
I have tried everything but it just fails. What should I do?
Two mockImplementation solved the problem!
const mocks = {
{
data: {
token: null
},
};
}
const mockMutation = jest.fn()
(useMutation as jest.Mock).mockImplementation(() => [mockMutation]);
(mockMutation as jest.Mock).mockImplementation(() => mocks);
For testing purposes, I setup a dependency injection on my react component. I'm not sure if it's the best practice.
I got this warning React Hook useEffect has a missing dependency:
Is there a better way to fix or to make my intent?
In my basic example, I have a service that fetch an "hello world" on an Api. My component use the service to fetch the data on loading.
I can easly test my component thanks to the dependency injection (with props), by inject some mock function.
## hello-world.js
import React, {useEffect, useState} from "react";
import {fetchHelloWorld} from "../services/fetch-hello-world";
import PropTypes from 'prop-types';
const HelloWorld = ({
fetchHelloWorld
}) => {
const [message, setMessage] = useState('');
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchHelloWorld().then(message => {
setIsLoading(false);
setMessage(message.response);
})
}, [fetchHelloWorld]
);
return (
<>
{isLoading ? "Loading" : message}
</>
);
}
HelloWorld.defaultProps = {
fetchHelloWorld: fetchHelloWorld
}
HelloWorld.propTypes = {
fetchHelloWorld: PropTypes.func.isRequired
}
export default HelloWorld;
## fetch-hello-world.js
export function fetchHelloWorld() {
return fetch("/controller/hello_world")
.then(res => res.json())
.catch(e => console.error(e));
}
in this way i can test my component like that :
import React from "react";
import {act, render, screen} from "#testing-library/react";
import HelloWorld from "./hello-world";
describe("Hello Wolrd", () => {
test('should display Loading when data not load', async () => {
render(<HelloWorld/>);
const linkElement = screen.getByText('Loading');
expect(linkElement).toBeInTheDocument();
});
test('should display data when loaded', async () => {
let fakeFetchHelloWorld = () => Promise.resolve({response: "Hello World"});
await act(async () => {
render(<HelloWorld fetchHelloWorld={fakeFetchHelloWorld}/>);
})
const linkElement = screen.getByText('Hello World');
expect(linkElement).toBeInTheDocument();
});
})
In react navigation (I could do this in App.ts too) I fire off the authentication like so:
export default function Navigation() {
authenticateUser();
...
}
export default function authenticateUser() {
const setLoadingUser = useStore((state) => state.setLoadingUser);
firebase.auth().onAuthStateChanged(async (authenticatedUser) => {
console.log('AuthenticateUser', authenticatedUser);
setLoadingUser(false);
if (authenticatedUser) {
useAuthenticate(authenticatedUser);
} else {
console.log('No user');
setLoadingUser(false);
}
});
...
}
And for the sake of simplicity, I will just print the user for now:
import { useEffect } from 'react';
export const useAuthenticate = (authenticatedUser) => {
useEffect(() => {
console.log('authenticatedUser', authenticatedUser);
}, [authenticatedUser]);
return true;
};
I believe that because I'm calling useAuthenticate inside the async firebase onAuthStateChanged function, React is throwing [Unhandled promise rejection: 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:]
How do I handle this?
This should be:
export default function authenticateUser() {
const {setAuthenticated} = useAuthenticate();
const setLoadingUser = useStore((state) => state.setLoadingUser);
firebase.auth().onAuthStateChanged(async (authenticatedUser) => {
console.log('AuthenticateUser', authenticatedUser);
setLoadingUser(false);
if (authenticatedUser) {
setAuthenticated(authenticatedUser);
} else {
console.log('No user');
setLoadingUser(false);
}
});
...
}
import { useEffect, useState } from 'react';
export const useAuthenticate = () => {
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
console.log('authenticatedUser', authenticated);
}, [authenticated]);
return {authenticated, setAuthenticated};
};
I am trying to use below custom hook but it's not working as expected
CustomHook:
import { useEffect, useState } from 'react'
import { createAPIInstance } from '../utils/api'
import { getBaseUrl } from '../utils/functions'
const BASE_URL = getBaseUrl()
const useGetAPI = (endpoint) => {
const [data, setData] = useState([])
const api = createAPIInstance()
const getData = async () => {
const response = await api.get(`${BASE_URL}${endpoint}`)
setData(response.data)
}
useEffect(() => {
getData()
}, [])
return data
}
export {
useGetAPI
}
Here is the custom hook usage
app.js
function App() {
const [details, setDetails] = useState({})
useEffect(() => {
const fetchData = async () => {
const details = useGetAPI('/some-api-endpoint')
setDetails(details)
}
fetchData()
}, [])
return (
<div></div>
)
}
Error on chrome console:
Uncaught (in promise) Invariant Violation: 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 fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
at invariant
React Version: 16.13.1
React-Dom Version: 16.13.1
For app.js :
Why are you initialise a function in the same location ?
Example :
const fetchData = async () => {
const details = useGetAPI('/some-api-endpoint')
setDetails(details)
}
useEffect(async () => {
fetchData()
}, [])
You don't export default your function.
Example :
export default function App() {
For CustomHook :
You don't say as your function is using async values.
Example :
const useGetAPI = async (endpoint) => {
useEffect(async () => {
I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}