Invariant Violation: Unable to find node on an unmounted component. Apollo - testing

I have some problems testing a Component inside a Create React App that returns a Query Component, I'm using jest and enzyme for testing. The error that I get is Invariant Violation: Unable to find node on an unmounted component.. Any ideas with what I'm doing wrong? What I'm trying to get is to test that the query component will return an array of components based on the data received from the server.
I tried using the methods posted in this medium article, but I can't get it to work at all.
// The component
export class MyWrapper extends React.Component {
render() {
return (
<List divided verticalAlign="middle" >
<Query query={query} >
{({ data, loading, error, refetch }) => {
if (loading) return <Loader />;
if (error) return <ErrorMessage />;
// set refetch as a class property
this.refetch = refetch;
return data.response
.map(el => (
<MyComponent
el={el}
/>
));
}}
</Query>
</List>
);
}
}
export default compose(
...//
)(MyWrapper);
// The test file
import React from "react";
import { MockedProvider } from "react-apollo/test-utils";
import query from "path/to/query";
import { MyWrapper } from "../MyWrapper";
import { props } from "./props";
const mocks = {
request: {
query,
},
result: {
data: {
response: [
// data
]
}
}
};
describe("<MyWrapper />", () => {
describe("rendering", () => {
it("renders <MyComponent />'s", async () => {
const wrapper = mount(
<MockedProvider mocks={mocks} removeTypename>
<MyWrapper {...props} />
</MockedProvider>
);
await new Promise(resolve => setTimeout(() => resolve(), 1000));
wrapper.update();
console.log(wrapper.debug());
});
});
});
This is the code snippet I tried to reproduce:
const wait = require('waait');
it('should render dog', async () => {
const dogMock = {
request: {
query: GET_DOG_QUERY,
variables: { name: 'Buck' },
},
result: {
data: { dog: { id: 1, name: 'Buck', breed: 'poodle' } },
},
};
const component = renderer.create(
<MockedProvider mocks={[dogMock]} addTypename={false}>
<Dog name="Buck" />
</MockedProvider>,
);
await wait(0); // wait for response
const p = component.root.findByType('p');
expect(p.children).toContain('Buck is a poodle');
});

After Googling to solve this for myself I found this.
According to this Git Issue the problem is in enzyme-adapter-react-16. EthanJStark said that updating to enzyme version 1.5.0 corrected it. I can confirm that the error stopped.
tldr;package.json – "enzyme-adapter-react-16": "^1.1",
+ "enzyme-adapter-react-16": "^1.5.0",

I was getting Invariant Violation: Unable to find node on an unmounted component too with TypeScript and Next.js in the mix.
After creating an isolated project which worked, I knew it had to be my codebase.
The stack trace seemed to stem at invariant (node_modules/react-dom/cjs/react-dom.development.js:55:15).
So in my case, upgrading from "react-dom": "16.5.2" to "react-dom": "16.7.0" fixed the issue for me, along with re-creating my yarn.lock file.

Related

How to update state on App start in react native

I am trying to read from the AsyncStorage and update my store ( using easy-peasy ) on app start.
My thought process was to call fetch the data from AsyncStorage using useEffect with the second argument of an empty array to only fetch once on app start and update the store with that value using an action.
But that doesn't work i get the error invalid hook call. Any insights on how to solve this or what the correct approach would be are appreciated !
App.ts
export default function App() {
useEffect(() => {
readData();
}, []);
return (
<StoreProvider store={store}>
<SafeAreaProvider>
<Navigation />
<StatusBar />
</SafeAreaProvider>
</StoreProvider>
);
}
// This needs to be called when app is started
const readData = async () => {
try {
const secret = await storage.getItem('secret');
const initializeState = useStoreActions(
(actions) => actions.initializeState
);
initializeState({
payload: {
secret,
},
});
console.log("executed action")
} catch (e) {
console.log('Failed to fetch the input from storage', e);
}
};
STORE
initializeState: action((state, payload) => {
state.secret = payload.secret
}),
ERROR
Failed to fetch the input from storage [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.]
export default function App() {
useEffect(() => {
setTimeout(()=>{ // add setTimeout() may be this work for you
readData();
},500)
}, []);
return (
<StoreProvider store={store}>
<SafeAreaProvider>
<Navigation />
<StatusBar />
</SafeAreaProvider>
</StoreProvider>
);
}
// This needs to be called when app is started
const readData = async () => {
try {
const secret = await storage.getItem('secret');
const initializeState = useStoreActions(
(actions) => actions.initializeState
);
initializeState({
payload: {
secret,
},
});
console.log("executed action")
} catch (e) {
console.log('Failed to fetch the input from storage', e);
}
};
You need to move you readData function into the App component since you're using a hook (useStorageActions) inside that function and you can only call hooks at the top level. You should take a look at the rules of react hooks.

React Navigation: Navigating outside a component

I am trying to navigate my app from outside of a component. Specifically, I am using a fetch interceptor and I want to navigate whenever an error response is received.
I followed the example here: https://reactnavigation.org/docs/navigating-without-navigation-prop/
However, my app is still giving me an error saying that either a navigator isn't rendered or the navigator hasn't finished mounting:
Screenshot of app with error message
As far as I can tell, neither of those situations apply. The app is loaded and rendered with a navigator in place before I try to actually navigate
My App.jsx:
// ... imports and so on ...
fetchIntercept.register({
response: (response) => {
if (response.status === 401) {
// Unverified subscription
RootNavigation.reset({ index: 0, routes: [{ name: 'Intercept' }] });
}
return response;
},
});
{ ... }
const InterceptNavigator = createStackNavigator(
{
Application: {
screen: ApplicationScreen,
},
Intercept: {
screen: SubscriptionInterceptScreen,
},
},
{
initialRouteKey: 'Application',
},
);
const App = createAppContainer(InterceptNavigator);
export default () => {
React.useEffect(() => {
RootNavigation.isMountedRef.current = true;
return () => { RootNavigation.isMountedRef.current = false; };
}, []);
return (
<NavigationContainer ref={RootNavigation.navigationRef}>
<App />
</NavigationContainer>
);
};
RootNavigation.js:
import * as React from 'react';
export const isMountedRef = React.createRef();
export const navigationRef = React.createRef();
export function navigate(name, params) {
if (isMountedRef.current && navigationRef.current) {
navigationRef.current.navigate(name, params);
}
}
export function reset(options) {
if (isMountedRef.current && navigationRef.current) {
navigationRef.current.reset(options);
}
}
I also inserted a number of console logs throughout and all of them showed that the app is loaded, that the navigationRef is current, and that the isMountedRef is also current before the app tries to navigate
Try .resetRoot() instead of .reset(). I think .reset() needs a state as an argument.
Found the solution. The issue is that I had a mixture of version 4 and version 5 code (and was referring to mixed documentation).
To fix the issue I removed references to version 5 code and then followed the steps on this page to get the navigator working: https://reactnavigation.org/docs/4.x/navigating-without-navigation-prop/

React Native Expo “AppLoading threw an unexpected error when loading” error

I have a React Native project with Expo, I installed Expo client on my Android phone. It used to work well so far. But even though I didn't change any code, I now get the following error when I scan the QR code from my phone. This error is shown on terminal and phone screen keeps blank.
import React from 'react';
import { Image } from 'react-native';
import { AppLoading } from 'expo';
import { Asset } from 'expo-asset';
import { Block, GalioProvider } from 'galio-framework';
import Screens from './navigation/Screens';
import { Images, articles, ditsTheme } from './constants';
// cache app images
const assetImages = [
Images.Onboarding,
Images.LogoOnboarding,
Images.Logo,
Images.Pro,
Images.DITSLogo,
Images.iOSLogo,
Images.androidLogo
];
// cache product images
articles.map(article => assetImages.push(article.image));
function cacheImages(images) {
return images.map(image => {
if (typeof image === 'string') {
return Image.prefetch(image);
} else {
return Asset.fromModule(image).downloadAsync();
}
});
}
export default class App extends React.Component {
state = {
isLoadingComplete: false,
}
render() {
if(!this.state.isLoadingComplete) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<GalioProvider theme={ditsTheme}>
<Block flex>
<Screens />
</Block>
</GalioProvider>
);
}
}
_loadResourcesAsync = async () => {
return Promise.all([
...cacheImages(assetImages),
]);
};
_handleLoadingError = error => {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
How can I solve this error?
In your _handleLoadingError method, you're using warn. While you should be using console.warn. This is what's breaking your app.
I was getting this error, the problem was with AppLoading. The issue turned out to be incorrect function name that I was calling from <AppLoading .. />
Note below, I was using Font.loadAsync (with missing c), fixing it made the font load correctly.
const getFonts = () => Font.loadAsyn({
'nanitu-regular' : require('./assets/fonts/EastSeaDokdo-Regular.ttf'),
'nanitu-bold' : require('./assets/fonts/NanumBrushScript-Regular.ttf')
})
;
You should add onError to AppLoading. As a like:
<AppLoading startAsync={getFonts} onFinish={() => {
setFontLoaded(true)
}} onError={console.warn} />

Why react-router-native does not render my component?

Trying to implement a protectedRoute utils for react-native project, which basicly looks for JWT. First it shows loading indicator, and if there is no JWT present it would redirect to /login.
const LoadingComponent = () => (
<View>
<ActivityIndicator/>
</View>
)
class PrivateRoute extends React.Component {
state = {
loading: true,
jwt: null,
}
componentDidMount() {
storage.retrieve('JWT').then(jwt => this.setState({ loading: false, jwt }))
}
render() {
const { children } = this.props;
const { jwt, loading } = this.state;
if (loading) {
return <Route {...children} component={LoadingComponent}/>
}
if (!jwt) {
return <Redirect to="/signup" />;
}
return <Route {...children} />
}
}
export default PrivateRoute;
this.props.children has all the required information to make a Route in the application. The original idea is that in case of loading we would just overwrite this.props.children.component with custom loading screen.
BUT the solution which does not give me error is (only warning):
if (loading) {
return LoadingComponent
}
I have tried also to manually inline component as
component={() => <View>{...}</View>
render={() => ...}
However it also ends up as the same error. Invariant Violation: Element type is invalid: expected a string ..... but got: undefined

When to make a Fetch call for React Native Component

I'm new to React Native and confused on how to properly utilize the provided Fetch API.
The call itself (as outlined here: http://facebook.github.io/react-native/docs/network.html) is straightforward, and I can log out a successful response, but when it comes time to render the data, it's undefined.
I would expect that I could define an empty 'movies' array, and then replace it by calling 'setState' from componentDidMount(), which would trigger a re-render. Is this assumption incorrect?
The code sample below results in the following error:
'undefined is not an object (evaluating 'allRowIDs.length')
Thanks in advance for any help!
import React, { Component } from 'react';
import { AppRegistry, ListView, Text, View } from 'react-native';
class ReactNativePlayground extends Component {
constructor() {
super();
this.state = {
movies: []
}
}
componentDidMount() {
fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
movies: responseJson.movies
})
})
.catch((error) => {
console.error(error);
});
}
render() {
return (
<View>
<Text>test</Text>
<ListView
dataSource={this.state.movies}
renderRow={(row) => <Text>{row.title}</Text>}
/>
</View>
)
}
}
AppRegistry.registerComponent('ReactNativePlayground', () => ReactNativePlayground);
That's because you need to place the data into a ListView.DataSource:
constructor (props) {
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (a, b) => a !== b
})
this.state = {
movies: ds.cloneWithRows([])
}
}
// Inside the response JSON:
this.setState({
movies: this.state.movies.cloneWithRows(responseJson.movies)
});
The React Native ListView docs demonstrate this kind of setup. Using the datasource allows for optimisations to be made when rendering lists of data (notice the rowHasChanged function for instance - which prevents needless re-rendering of a row when the data hasn't altered.