Getting props through HOC when mounting in Enzyme - reactjs

I am trying to test some code that uses firebase. I am implementing the firebase-mock library. The problem I'am encountering now is that most all the components I should test get the firebase instance from a HOC (I have a class with the firebase methods I am using that is provided through the context API in the index.js and consumed via a withFirebase HOC, the wrapped component will have firebase in its props).
In this case the code I am trying to test is the following:
// mount.js
import React, { Component } from 'react';
import { withFirebase } from '../../components/Firebase';
class mount extends Component {
state = {
data: null,
};
ref = this.props.firebase.db.ref('/testing');
componentDidMount() {
// Fetch from testing ref
this.ref.on('value', snap => {
this.setState({ data: snap });
});
}
componentWillUnmount() {
this.ref.off('value');
}
render() {
return <div />;
}
}
export default withFirebase(mount);
In my test file I'm doing the following:
describe('Component mount.js', () => {
it.only('fetches', () => {
const wrapper = mount(<Mount />);
console.log(wrapper.prop());
console.log(wrapper.state().data);
});
});
This fails because this.props.firebase is null.
How could I solve this so that I can continue and finally mock firebase calls as i was intending.
I'm guessing that the problem is how to use the Context API in Enzyme, but I'm not sure.

The message you are getting is because enzyme.mount() is returning the withFirebase HOCcomponent, not the <Mount> component that you expect. what you need to do is "find" the contained component. In your example I think myContainedComponent = wrapper.find('Mount') would return the component that you could then do console.log(myContainedComponet.props); There are a lot of answers to similar questions about using Enzyme to test HOC and their enclosed components. I am using React 17 which is not supported by Enzyme.mount() so I have to use shallow. Again there are answers related to doing wrapper = shallow(shallow(<BurgerBuilder/>).get(0)); but these don't work for my setup either.
What is working for me is:
wrapper = shallow(<BurgerBuilder/>);
instance = wrapper.find('BurgerBuilder').dive().instance();
jest.spyOn(instance, 'addIngredientHandler');
NOTE: this is the export for the BurgerBuilder component.
export default withErrorHandler(BurgerBuilder, axiosOrders);
In this example, instance holds the class instance of the contained component rather than the HOC, withErrorHandler.
One of the interesting things about my example is that 'addIngredientHandler' is an arrow function in my class. There are other threads that talk about the complexities of testing class member arrow functions in React. (BTW, you do not need to do instance.forceUpdate(); )
In the interest of full disclosure, I am building my testing skills as I learn React. The components I am testing were developed while running through the Udemy course: React - The Complete Guide (incl Hooks, React Router, Redux)

Related

Using React.Context with Nextjs13 server-side components

Next13 was released a week ago, and I am trying to migrate a next12 app to a next13.
I want to use server-side components as much as possible, but I can't seem to use
import { createContext } from 'react';
in any server component.
I am getting this error:
Server Error
Error:
You're importing a component that needs createContext. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
,----
1 | import { createContext } from 'react';
: ^^^^^^^^^^^^^
`----
Maybe one of these should be marked as a client entry with "use client":
Is there an alternative here or do I have to resort to prop drilling to get server-side rendering?
It seems like I can use createServerContext
import { createServerContext } from 'react';
If you're using Typescript and React 18, you'll also need to add "types": ["react/next"] to your tsconfig.json compiler options, since this is a not-yet-stable function.
This is a new feature from React's SSR to recognize whether a component is client-side or server-side. In your case, createContext is only available on the client side.
If you only use this component for client-side, you can define 'use client'; on top of the component.
'use client';
import { createContext } from 'react';
You can check this Next.js document and this React RFC for the details
According to Next.js 13 beta documentation, you cannot use context in Server Components:
In Next.js 13, context is fully supported within Client Components, but it cannot be created or consumed directly within Server Components. This is because Server Components have no React state (since they're not interactive), and context is primarily used for rerendering interactive components deep in the tree after some React state has been updated
However, there are alternative ways to handle data in the new approach, depending on your case. F.e. if you fetched the data from the server in a parent component and then passed it down the tree through Context, you can now fetch the data directly in all the components that depend on this data. React 18 will dedupe (de-duplicate) the fetches, so there are no unnecessary requests.
There are more alternatives in the documentation.
I've made a tiny package to handle context in server components, works with latest next.js, it's called server-only-context:
https://www.npmjs.com/package/server-only-context
Usage:
import serverContext from 'server-only-context';
export const [getLocale, setLocale] = serverContext('en')
export const [getUserId, setUserId] = serverContext('')
import { setLocale, setUserId } from '#/context'
export default function UserPage({ params: { locale, userId } }) {
setLocale(locale)
setUserId(userId)
return <MyComponent/>
}
import { getLocale, getUserId } from '#/context'
export default function MyComponent() {
const locale = getLocale()
const userId = getUserId()
return (
<div>
Hello {userId}! Locale is {locale}.
</div>
)
}
This is the code for it, it's really simple:
import 'server-only'
import { cache } from 'react'
export default <T>(defaultValue: T): [() => T, (v: T) => void] => {
const getRef = cache(() => ({ current: defaultValue }))
const getValue = (): T => getRef().current
const setValue = (value: T) => {
getRef().current = value
}
return [getValue, setValue]
}

Extending AWS Amplify Auth components in typescript

So I'm trying to extend the existing Auth components in AWS Amplify such as SignIn, SignUp, etc. and override the showComponent() function to return a custom form as detailed in this post: https://blog.kylegalbraith.com/2018/11/29/how-to-easily-customize-the-aws-amplify-authentication-ui/
I'm using typescript for my nextjs project and I'm getting the following error: when I try to throw the custom component under the Authenticator component:
[ts]
JSX element type 'CustomSignUp' is not a constructor function for JSX elements.
Type 'CustomSignUp' is missing the following properties from type 'ElementClass': render, context, setState, forceUpdate, and 3 more.
Here's my _app.tsx:
import {SignUp} from 'aws-amplify-react/dist/Auth/SignUp';
class NewApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps }
}
render() {
const {props} = this as any
const {Component, pageProps} = props
return (
<Container>
<Authenticator hide={[SignUp]}>
<CustomSignUp/>
<Component {...pageProps}/>
</Authenticator>
</Container>
)
}
}
export default NewApp;
And the CustomSignUp.tsx is just something stupidly simple for demonstration purposes:
class CustomSignUp extends SignUp {
_validAuthStates;
constructor(props) {
super(props);
this._validAuthStates = ['signUp'];
}
showComponent(theme) {
return(
<div>
Hi!
</div>
)
}
}
export default CustomSignUp;
What's the way to do this correctly?
Per your request above I will provide more detail. As I mentioned in my comment, we found implementing and customizing the AWS Amplify components to be restrictive and difficult. We therefore decided to simply build out our own UI Components as we normally would, manage authentication globally with the Amplify HUB module and a Cognito Auth method helper class. Finally, we pushed Cognito user data down through our components with our own simple HOC.
To start, in a Component mounted when your app first loads, you can import Hub from aws-amplify to add any event listeners relevant to your app in the Component -- perhaps in the constructor -- including listeners to monitor auth state:
Hub.listen("auth", data => {
const { payload } = data;
if (payload.event === "signOut") {
props.navigation.navigate("SigninScreen");
}
}
You can listen/respond to auth changes throughout your app, even if the component in which you established the listeners unmounts.
Next, you can build a simple class with the various methods from the Auth module, again imported from aws-amplify, encapsulating functionality such as Auth.currentAuthenicatedUser, Auth.signUp, etc. With our own UI we simply attached/invoked the Cognito methods at the appropriate places and time.
If you decide to take this route, the last gap to fill is how to pass down the data from Auth's currentAuthenticatedUser method to your components (as Amplify's out of the box HOC would do). You can make your own HOC which fetches user data by Auth.currentAuthenticatedUser(), and pass the received data via props to any Component it wraps - fairly straightforward.
Because we were using graphql/Apollo, in our case we decided to use Apollo Client local resolvers to retrieve/pass Cognito user data. You can read more about Apollo Client local resolvers here here if you're interested.

Error while doing unit test in react-redux connected component

I am trying to test the connected component(react-redux) with jest-enzyme. I am using react-redux-mock store. When I run my test to find one div in the component it gives me this error.
Invariant Violation: Passing redux store in props has been removed and does not do anything. To use a custom Redux store for specific components, create a custom React context with React.createContext(), and pass the context object to React-Redux's Provider and specific components like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. You may also pass a {context : MyContext} option to connect
I did mount and tested just component without redux it works but I want to do a > shallow test.
describe("Input Component", () => {
let wrapper;
let store;
beforeEach(() => {
store = mockStore(initialState);
wrapper = shallow(<Input store={store} />);
});
it("should rendder without error", () => {
expect(wrapper.find("div")).toHaveLength(1);
});
});
How do you import your component?
if your are importing it with import App from './yourpath/App' for example, ou're actually holding the wrapper component returned by connect(), and not the App component itself.
In order to be able to test the App component itself without having to deal with the decorator, you must to also export the undecorated component:
import { connect } from 'react-redux'
// Use named export for unconnected component (for tests)
export class App extends Component {
/* ... */
}
// Use default export for the connected component (for app)
export default connect(mapStateToProps)(App)
And import it in your test file like that:
import { App } from './yourpath/App'

Mocking Redux store when testing React components?

I'm using React and Redux. I have a component which loads ChildComponent and depending on Redux's state will also load MainComponent
const ChooseIndex = ({ appInitMount }) => {
return (
<>
<ChildComponent />
{!appInitMount && <MainComponent />}
</>
);
};
const mapStateToProps = ({ main }) => {
return {
appInitMount: main.appInitMount
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChooseIndex);
I'm trying to write a test to check that ChildComponent is loaded:
import React from "react";
import { render } from "react-testing-library";
import ChooseIndex from "../choose-index";
test("ChooseIndex should call ChildComponent", () => {
const wrapper = render(
<ChooseIndex />
);
});
I get this error:
Error: Uncaught [Invariant Violation: Could not find "store" in either
the context or props of "Connect(ChooseIndex)". Either wrap the root
component in a , or explicitly pass "store" as a prop to
"Connect(ChooseIndex)".]
Should I mock Redux by passing an object literal to ChooseIndex? Or should I create a Redux store (as my real application does) for every test?
Try to render your component like this:
render(
<Provider store={store}>
<ChooseIndex />
</Provider>
)
And pass the actual store you use in your app. In this way, you're testing the real logic that you'll use in production. You also don't care what actions get dispatched and what's in the state. You look at what gets rendered and interact with the UI—which is what matters in the end.
Separating the component from Redux and testing the two in isolation is against the whole point of react-testing-library. You want to test your app as a real user would.
If you check out the writing tests section of the redux docs, there is an example of testing a connected component.
when you import it [A redux connected component], you're actually holding the wrapper component returned by connect(), and not the App component itself. If you want to test its interaction with Redux, this is good news: you can wrap it in a with a store created specifically for this unit test. But sometimes you want to test just the rendering of the component, without a Redux store.
In order to be able to test the App component itself without having to deal with the decorator, we recommend you to also export the undecorated component
As with most unit tests, you really want to be testing your components, and not that redux is working correctly. So the solution for you is to export both the component and the connected component, while only testing the component itself, and providing whatever props redux is passing to your component.
import { connect } from 'react-redux'
// Use named export for unconnected component (for tests)
export class App extends Component {
/* ... */
}
// Use default export for the connected component (for app)
export default connect(mapStateToProps)(App)

ES6 React Redux syntax clarification

I'm new to ES6 and Redux. Im looking at some code and trying to understand what is going on in this new ES6 syntax.
I feel like this may be simple but i am not understanding it and it might help someone else in a similar position to me.
i want to know how the following code is creating a react element. im familiar with the React.createClass method, but that doesnt seem to be stated here or at least not explicitly. i can see React is imported, but it isnt mentioned in the rest of the code. so then how the FileTable get turned into a react component?
I can see the const variable FileTable seems to contain what would usually go in the render method of React.createClass, but if that is the case, where would methods like componentDidMount, componentDidUpdate, etc be defined?
Any help on this is greatly appreciated.
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
const FileTable = ({ fileList, getFileList}) => {
return (
<ul className="filterable-table">
{fileList.map((file)=><li>{file.fileName}</li>)}
</ul>
);
};
FileTable.propTypes = {
fileList: PropTypes.array,
};
const mapStateToProps = (state) => {
return {
fileList: state.fileList
};
};
const mapDispatchToProps = (dispatch) => {
return {
getFileList: () => dispatch(actions.getFileList())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FileTable);
You can create react components in 3 ways - React.createClass, ES6 class or
Stateless (pure) function component. This is a stateless component, which means that it doesn't have state, life cycle methods (like componentDidMount or componentDidUpdate), and refs, and as you surmised it's similar to the render method of a react class.
Whenever you need a purely representational dumb component you can use a stateless component, due to its brevity. It goes nicely with redux, as the connect create a smart component that wraps the stateless method.
Regarding performance, stateless components don't have any performance gain over ES6 class component without state. However, Facebook stated that in the future there will be some optimizations.
It doesn't have to be declared here as a React component; React knows about pure functions.
Pure functions don't need to inherit from Component. They're not appropriate for all component types, but for simple HTML renders they're preferred (e.g., see eslint-plugin-react prefer-stateless-function.
Pure functions don't have component lifecycles, associated methods, etc.

Resources