How to run LogRocket in expo go : Invariant Violation: Native module cannot be null - expo

When run LogRocket in expo go has this error:
Invariant Violation: Native module cannot be null.
at node_modules/react-native/Libraries/Core/ExceptionsManager.js:104:6 in reportException
at node_modules/react-native/Libraries/Core/ExceptionsManager.js:172:19 in handleException
at node_modules/react-native/Libraries/Core/setUpErrorHandling.js:24:6 in handleError
at node_modules/expo-error-recovery/build/ErrorRecovery.fx.js:12:21 in ErrorUtils.setGlobalHandler$argument_0

Based on this guide https://docs.expo.dev/bare/using-expo-client/#use-conditional-inline-requires-to-provide-fallbacks
create a file LogRocketInit.js
import Constants from 'expo-constants';
import { useEffect } from 'react';
export default function LogRocketInit() {
useEffect(() => {
if (Constants.appOwnership !== 'expo') {
const LogRocket = require('#logrocket/react-native');
LogRocket.init('****');
}
}, []);
return null;
}
then add it to App.js as normal
export default function App() {
return (
...
<LogRocketInit />
...
}
in case you need to use it in another place, use this
if (Constants.appOwnership !== 'expo') {
const LogRocket = require('#logrocket/react-native');
....
}

Related

How to override a mocked module (using jest.mock) set up in setupFilesAfterEnv configured for jest?

I am trying to mock the init method provided by sentry-expo and so far, this is what I have come up with:
setupFilesAfterEnv.ts
import '#testing-library/jest-native/extend-expect';
import * as Sentry from 'sentry-expo';
import sentryTestkitSuite from 'sentry-testkit';
const DUMMY_DSN = 'https://acacaeaccacacacabcaacdacdacadaca#sentry.io/000001';
const { sentryTransport } = sentryTestkitSuite();
// https://stackoverflow.com/questions/44649699/service-mocked-with-jest-causes-the-module-factory-of-jest-mock-is-not-allowe
// Cannot use the imported module as a value directly
const mockSentryTransport = sentryTransport as jest.Mocked<
typeof sentryTransport
>;
jest.mock('sentry-expo', () => ({
...jest.requireActual('sentry-expo'),
init: (options?: Sentry.SentryExpoNativeOptions) => ({
...options,
transport: mockSentryTransport,
}),
}));
beforeAll(() =>
Sentry.init({
dsn: DUMMY_DSN,
release: 'test',
tracesSampleRate: 1,
beforeSend(event) {
return {
...event,
extra: { os: 'mac-os' },
};
},
}),
);
beforeEach(() => {
sentryTestkitSuite().testkit.reset();
});
All the test cases which have used Sentry to capture exceptions successfully pass.
Now, I have created a file for adding standard crash-reporting utilities:
crash-reporting.ts
import * as Sentry from 'sentry-expo';
import { getEnvironmentConfig } from '#utils/environment/environment';
const routingInstrumentation =
new Sentry.Native.ReactNavigationInstrumentation();
export const initialiseCrashReporting = () => {
return Sentry.init({
dsn: getEnvironmentConfig()?.sentryDSN,
// Enable it only when you install the Expo development build on your device/simulator
// If you enable it while running the app in Expo Go, native dependencies will not work as expected such as Sentry
enableInExpoDevelopment: __DEV__,
debug: __DEV__, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production,
environment: getEnvironmentConfig()?.appEnv ?? 'development',
tracesSampleRate: __DEV__ ? 1 : 0.2,
integrations: [
new Sentry.Native.ReactNativeTracing({
tracingOrigins: ['localhost', /^\//],
routingInstrumentation,
}),
],
});
};
export const { wrap: sentryWrap } = Sentry.Native;
I am trying to test the above crash-reporting module like so:
crash-reporting.test.ts
import * as Sentry from 'sentry-expo';
import { initialiseCrashReporting } from './crash-reporting';
jest.mock('sentry-expo', () => {
const originalModule = jest.requireActual('sentry-expo');
return {
...originalModule,
init: jest.fn(),
};
});
describe('Crash Reporting Test Suite', () => {
it('should initialise sentry', () => {
const initSpy = jest.spyOn(Sentry, 'init');
initialiseCrashReporting();
expect(initSpy).toHaveBeenCalled();
});
});
Even though initialiseCrashReporting gets called, spyOn never catches the event where init gets called.
I realised that the globally mocked sentry-expo never gets overridden with the one in the crash-reporting.test.ts file.
I have 2 below-given questions related to this problem:
How can I override the globally mocked modules? Or how can I be assured that by calling initialiseCrashReporting, I am initialising sentry?
Can we override global beforeall for specific test cases?
Thanks in anticipation!

Mock objection model dependecy in NestJs with Jest

I'm trying to make unit test with nestjs and objection. The problem I have is that I can't mock the "User" Model that is injected with the decorator "#InjectModel". I searched a lot to find a solution but I didn't find anything.
users.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '#nestjs/common';
import { CreateUserDto } from './create-user.dto';
import { User } from 'src/app.models';
import { InjectModel } from 'nestjs-objection';
#Injectable()
export class UsersService {
constructor(
#InjectModel(User) private readonly userModel: typeof User,
) {}
async create(createUserDto: CreateUserDto) {
try {
const users = await this.userModel.query().insert(createUserDto);
return users
} catch (err) {
console.log(err)
throw new HttpException(err, HttpStatus.BAD_REQUEST);
}
}
}
users.service.spec.ts
import { Test, TestingModule } from "#nestjs/testing";
import { UsersService } from "../src/users/users.service";
import { CreateUserDto } from "src/users/create-user.dto";
import { User } from "../src/app.models";
import { getObjectionModelToken } from 'nestjs-objection';
describe('userService', () => {
let userService: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: User,
useValue: {}
},
],
}).compile();
userService = module.get<UsersService>(UsersService);
});
it('Should be defined', () => {
expect(userService).toBeDefined();
});
it('Should add pin to a created user', async () => {
const createUserDTO: CreateUserDto = {
email: 'mockEmail#mock.com',
userName: 'user'
}
const res = await userService.create(createUserDTO)
expect(res).toHaveProperty('pin')
});
I tried to use import { getObjectionModelToken } from 'nestjs-objection'; inside provider like this:
providers: [
UsersService,
{
provide: getObjectionModelToken(User),
useValue: {}
},
],
I got this error
It asks for a "connection" but I don't know what to put on it.
I suppose "getObjectionModelToken" is the function to mock the "InjectModel". When I pass an empty string
I got this error:
● Test suite failed to run
Cannot find module 'src/app.models' from '../src/users/users.service.ts'
Require stack:
C:/nestjs-project/nestjs-knex/src/users/users.service.ts
users.repository.spec.ts
1 | import { HttpException, HttpStatus, Inject, Injectable } from '#nestjs/common';
2 | import { CreateUserDto } from './create-user.dto';
> 3 | import { User } from 'src/app.models';
| ^
4 | import {
5 | InjectModel,
6 | synchronize,
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/resolver.js:491:11)
at Object.<anonymous> (../src/users/users.service.ts:3:1)
If I change the path it breaks the correct functionality of the app
That looks like an error from jest not understanding what src/* imports are. Either use relative imports rather than absolute (e.g. use import { User } from '../app.models') or tell jest how to resolve src/* imports via the moduleNameMapper in your jest.config.js or package.json
{
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/path/to/src/$1"
}
}
I think based on the error your /path/to/src should be ../src but I'm not 100% sure, so make sure you set that correctly.

How to use getServerSideProps for every pages in next.js?

I have set a cookie with nookies which store the values of all the products selected by user.
I want to fetch the cookie in server side using getServerSideProps and pass the value as props. I have to display the value of cookie on all pages.
When I tried getServerSideProps in _app.js. It did not worked and it did not even run the code.
Is there any way to do it?
As of now, there isn't a built-in way to do it, so I've resorted to doing the following.
First, I created a file that holds the getServerSideProps function I want to run on every page:
// lib/serverProps.js
export default async function getServerSideProps(ctx) {
// do something
return {
// data
};
}
Then in every page (yes, every, I can't find a workaround; it might even be helpful if you don't need the code to execute on server pages), do:
import getServerSideProps from "../lib/serverProps";
// other stuff...
export { getServerSideProps };
or
// other stuff...
export { default as getServerSideProps } from "../lib/serverProps";
If you want to add other code to run inside getServerSideProps for a specific page, you could do something along the lines...
import serverProps from "../lib/serverProps";
// other stuff...
export async function getServerSideProps(ctx) {
// do custom page stuff...
return {
...await serverProps(ctx),
...{
// pretend this is what you put inside
// the return block regularly, e.g.
props: { junk: 347 }
}
};
}
getServerSideProps does not work in _app.js. see docs.
you could use the older getInitialProps in your custom app component but then the automatic static optimisation is disabled, which is something Next.js bets on heavily.
it might be worth digging into your cookie use case and figure out if you really need to read it on the server side.
For those wanting to share state received from a page's getServerSideProps function to global components in pages/_app.tsx, I've pieced this solution together.
Create a shared getServerSideProps function to include on all pages
Create a shared useSetUserStorage custom hook to include on all pages
Listen for localStorage changes with custom event listener in global component (e.g. GlobalNav)
It's a work around, but is working for me so far (note that it includes some specifics to my use of getServerSideProps function).
It's a fair amount of code but hopefully this helps someone:
// src/pages/_app.tsx
import type { AppProps } from "next/app";
import GlobalNav from "../components/GlobalNav";
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<>
<GlobalNav /> // <— your global component
<Component {...pageProps} />
</>
);
}
export default MyApp;
// src/utils/getServerSideProps.ts
import { ppinit, ppsession, sess } from "../authMiddleware";
import nc from "next-connect";
import { NextApiRequest, NextApiResponse } from "next";
import { User } from "../types/types";
interface ExtendedReq extends NextApiRequest {
user: User;
}
interface ServerProps {
req: ExtendedReq;
res: NextApiResponse;
}
interface ServerPropsReturn {
user?: User;
}
//
// Here we use middleware to augment the `req` with the user from passport.js
// to pass to the page
// src: https://github.com/hoangvvo/next-connect/tree/21c9c73fe3746e66033fd51e2aa01d479e267ad6#runreq-res
//
const getServerSideProps = async ({ req, res }: ServerProps) => {
// ADD YOUR CUSTOM `getServerSideProps` code here
const middleware = nc()
.use(sess, ppinit, ppsession)
.get((req: Express.Request, res: NextApiResponse, next) => {
next();
});
try {
await middleware.run(req, res);
} catch (e) {
// handle the error
}
const props: ServerPropsReturn = {};
if (req.user) props.user = req.user;
return { props };
};
export interface Props {
user?: User;
}
export default getServerSideProps;
// src/hooks.ts
import { useEffect } from "react";
import { User } from "./types/types";
export const useSetUserStorage = (user?: User) => {
useEffect(() => {
if (user) {
localStorage.setItem("user", JSON.stringify(user));
} else {
localStorage.removeItem("user");
}
// whether setting or removing the user, dispatch event so that `GlobalNav`
// component (which is above the page implementing this hook in the
// component hierarchy) can be updated to display the user status. we
// can't use `window.addEventListener('storage', handler)` as this only
// works for listening for events from other pages
document.dispatchEvent(new Event("localStorageUserUpdated"));
});
return null;
};
// src/pages/index.tsx (or any page)
import { useSetUserStorage } from "../hooks";
import { Props } from "../utils/getServerSideProps";
export { default as getServerSideProps } from "../utils/getServerSideProps";
export default function Home({ user }: Props) {
useSetUserStorage(user);
return (
<>
<h1>Welcome to my app {user?.username}</h1>
</>
);
}
// src/components/GlobalNav.ts (or another global component)
import { useEffect, useState, MouseEvent } from "react";
import { User } from "../types/types";
const GlobalNav = () => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const handleUserLocalStorage = () => {
const userString = localStorage.getItem("user");
try {
if (userString) {
setUser(JSON.parse(userString));
} else {
setUser(null);
}
} catch (e) {
// handle parse error
}
};
handleUserLocalStorage();
// this component (`GlobalNav`) lives at the application level, above the
// pages, but the pages receive the user object from `getServerSideProps`,
// so this listener listens for when a page tells us the user object has
// changed so we can update the `user` state here.
document.addEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
false,
);
return () => {
// remove listener if component unmounts
document.removeEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
);
};
}, []);
return (
<div>
{user?.username}
</div>
);
};
export default GlobalNav;
I used a slightly different technique. Every page, in my case, has its own getServerSideProps and I was looking for a more functional approach. Also I'm using GraphQL, but the idea is the same no matter which data fetching API you choose. A regular getServerSideProps would look like this -
export const getServerSideProps: GetServerSideProps = async (context) => {
const { slug } = context.query
const { data: profile } = await client.query({ query: GetProfileDocument, variables: { slug } })
return {
props: {
...(await getSelf(context)),
profile: profile?.GetProfile[0],
},
}
}
In the props you can see the await statement, which is called in all pages. And in the few cases I don't need it, it's gone. This is what getSelf looks like -
const getSelf = async (context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) => {
const session = await getSession(context)
let self = null
if (session) {
const { data } = await client.query({
query: GetProfileDocument,
variables: { secret: session?.secretSauce as string },
})
self = data.GetProfile[0]
}
return { self, sessionData: session }
}
Hope it helped.

How to get UserId and set it as a global variable using useContext, useState and useEffect in React-Native?

I have an app built with React-Native, Amplify, AppSync and Cognito and when it loads I would like to save the USER ID and USER TYPE as a global state that can be accessed on every screen.
The user id and user type (Teacher or Student) will never change as these are created on signup.
import React, { useEffect, useState, useReducer } from 'react';
import {AppRegistry} from 'react-native';
import {name as appName} from './app.json';
import App from './src/AppNavigation';
import Amplify, { API, graphqlOperation, Auth } from 'aws-amplify';
import awsmobile from './aws-exports';
import { getUser } from './src/graphql/queries';
Amplify.configure(awsmobile);
export const UserContext = React.createContext()
function MyApp() {
const [userContext, setUserContext] = useState({})
const getUserIdAndType = async () => {
try {
// get User data
const currentUser = await Auth.currentAuthenticatedUser();
const userId = await currentUser.signInUserSession.accessToken.payload.sub;
// get user data from AppSync
const userData = await API.graphql(graphqlOperation(getUser, { id: userId }));
setUserContext({ userId: userId, userType: userData.data.getUser.userType })
} catch (err) {
console.log('error', err);
}
}
useEffect(() => {
getUserIdAndType()
}, [])
return (
<UserContext.Provider value={userContext}>
<App />
</UserContext.Provider>
);
}
AppRegistry.registerComponent(appName, () => MyApp);
Then when I want to use the context state I do as follows:
import { useContext } from 'react';
import { UserContext } from '../../../index';
function Loading ({ navigation }) {
const userContext = useContext(UserContext)
if (userContext.userId != '') {
navigation.navigate('AppTabs');
} else {
navigation.navigate('Auth');
}
}
export default Loading;
Or to get which screen to show (Teacher or Student)...
import { useContext } from 'react';
import { UserContext } from '../../../index';
function LoadingProfile ({ navigation }) {
const userContext = useContext(UserContext)
if (userContext.userType === 'Teacher') {
navigation.navigate('TeacherScreen');
} else if (userContext.userType === 'Student') {
navigation.navigate('StudentScreen');
}
}
export default LoadingProfile;
When the app loads it says the userContext.userId and userContext.userType are empty so it is not saving the state when I set it in the getUserIdAndType() function.
-
****** If I rewrite the App file (INSTEAD OF USING THE HOOKS useState, useEffect) I just declare the values then it works... so I am obviously not using the hooks or async getUserIdAndType() correctly. ******
import React, { useEffect, useState, useReducer } from 'react';
import {AppRegistry} from 'react-native';
import {name as appName} from './app.json';
import App from './src/AppNavigation';
import Amplify, { API, graphqlOperation, Auth } from 'aws-amplify';
import awsmobile from './aws-exports';
import { getUser } from './src/graphql/queries';
Amplify.configure(awsmobile);
export const UserContext = React.createContext()
function MyApp() {
const userContext = {
userId: '123456789', // add the user id
userType: 'Teacher', // add the user type
}
return (
<UserContext.Provider value={userContext}>
<App />
</UserContext.Provider>
);
}
AppRegistry.registerComponent(appName, () => MyApp);
change this :
<UserContext.Provider value={{userContext}}>
<App />
</UserContext.Provider>
to this :
<UserContext.Provider value={userContext}>
<App />
</UserContext.Provider>
you've added an extra curly bracket " { "

Redux-form 6.0.1 unit test issue

I just upgraded Redux-Form from 5.3.2 to 6.0.1, but the unit test that works in 5.3.2 failed in 6.0.1.
/* MyForm.jsx */
...
import { Field, reduxForm } from 'redux-form';
class MyForm extends Component {
...
<form onSubmit={handleSubmit(...)}>
...
}
export default reduxForm({
form: 'myForm'
})(MyForm);
I mounted the form reducer before render the form:
import {reducer as formReducer} from 'redux-form';
const myReducer = combineReducers({
...
form: formReducer
});
Here is the store, created in top level component:
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
const store = createStoreWithMiddleware(myReducer);
My test case (Karma + jasmine), which works in 5.3.2, but failed in 6.0.1
/* form.test.js */
import React, { PropTypes } from 'react';
import TestUtils from 'react/lib/ReactTestUtils';
import findDOMNode from 'react/lib/findDOMNode';
import { Provider } from 'react-redux';
...
import MyForm from '../MyForm';
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
const store = createStoreWithMiddleware(myReducer);
describe('MyForm', () => {
beforeAll(function() {
this.props = {
...
store: store
}
});
it('should render', function() {
const element = TestUtils.renderIntoDocument(
<MyForm {...this.props} />
);
expect(element).toBeTruthy();
});
error: Invariant Violation: Could not find "store" in either the context or props of "Connect(ConnectedField)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(ConnectedField)".
If I use Provider to pass in store, will get another error:
it('should render', function() {
const element = TestUtils.renderIntoDocument(
<Provider store={ store }>
{ () => <MyForm {...this.props} />}
</Provider>
);
expect(element).toBeTruthy();
});
*
error: Invariant Violation: onlyChild must be passed a children with exactly one child.
ERROR: 'Warning: Failed propType: Invalid prop `children` supplied to `Provider`, expected a single ReactElement.'
*
Any ideas why the test failed? I searched online but could not find information that specific for this topic.
Thanks,