Jest/Enzyme | Redux prop is not defined in test - javascript

I am using React-Redux, in a connected component and I want to test if a particular component is rendered. In order for that component to render 2 things must be true:
ListUsers must be an empty array
The securityMode should be basic.
I have already defined the securityMode in my component Props, with no problem. But the ListUsers prop, is coming through redux.
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
This is my component logic that should be tested:
renderNoResourceComponent = () => {
const { usersList, securityMode } = this.props;
const { selectedGroups } = this.state;
const filteredData = filterUserData(usersList, selectedGroups);
if (filteredData && filteredData.length === 0 && securityMode === 'BASIC') {
return (
<div className="center-block" data-test="no-resource-component">
<NoResource>
.............
</NoResource>
</div>
);
}
return null;
};
And this is the test I wrote:
describe('BASIC securityMode without Data', () => {
const props = {
securityMode: 'BASIC',
listUsers: () => {},
usersList: [] // This is the redux prop
};
it('should render NoResource component', () => {
const wrapper = shallow(<UsersOverviewScreen {...props} />);
const renderUsers = wrapper.find(`[data-test="no-resource-component"]`);
expect(renderUsers).toHaveLength(1);
});
});
But I get an error saying the userLists is not defined. How do I pass this redux prop so my component would pass. `I also need that prop for another set of tests, that needs data, which I need to mock.
Can someone guide me through this? Thank you..

What you want to do is export the component before its connocted to Redux and pass all the props it needs manually:
export class UsersOverviewScreen extends Component {
// ... your functions
render() {
return (
// ... your componont
);
}
}
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
export default connect(mapStateToProps)(UsersOverviewScreen);
Now, in your tests you can import { UsersOverviewScreen } form 'path/to/UsersOverviewScreen';. You can create the props and pass it to the component like this:
const mockUsersLists = jest.fn(() => usersList || []);
const wrapper = shallow(<UsersOverviewScreen {...props} usersList={mockUsersLists} />);

Related

How do I call a function from component that is not a child nor parent?

Component A
const query = ....
getData(query);
Component B
//here I want to call it using component's A argument
You would probably have a parent around these two components. You can use the parent to share the function:
function Parent() {
const [ dataWithQuery, setDataWithQuery ] = useState(null);
return (
<>
<ComponentA setDataWithQuery={ setDataWithQuery } />
<ComponentB dataWithQuery={ dataWithQuery } />
</>
);
}
and in Component A you'd have:
function ComponentA({ setDataWithQuery }) {
const yourQueryHere = '...'
function getData(query) { ... }
useEffect(() => {
setDataWithQuery(() => getData(yourQueryHere));
}, []);
...
}
Notice the line where I setDataWithQuery(...), I created a new function that calls getData with your query variable. This is how you save the function parameter to be used outside of ComponentA.
and in Component B you'd have:
function ComponentB({ dataWithQuery }) {
useEffect(() => {
if(dataWithQuery != null) {
const data = dataWithQuery();
}
}, [ dataWithQuery ]);
...
}
But there is no real reason to structure like this. Why not pass up the query from ComponentA to a parent, get the data from the parent using that query, then pass that data to ComponentB?
Edit: you could also look up React Contexts for sharing without passing up and down parent/child. There would still need to be a parent around.
Do you mean, you want to pass a query down to ComponentA, get the data from within it, then use that data in ComponentB?
If that is not what you want and you want to use the same query in each one, then just keep the query parameter in the component above them post and pass them down in props.
If the former is what you want:
Parent Component:
import "./styles.css";
import ComponentA from "./ComponentA";
import ComponentB from "./ComponentB";
import { useState } from "react";
export default function App() {
const [query, setQuery] = useState("get some data");
const [data, setData] = useState({});
return (
<div className="App">
<ComponentA query={query} setData={setData} />
<ComponentB data={data} />
</div>
);
Component A
import React, { useEffect } from "react";
function ComponentA({ query, setData }) {
useEffect(() => {
if (query !== "") {
getData(query);
}
}, [query]);
async function getData(query) {
//do some stuff
const result = { id: "1", message: "I am data" };
setData(result);
}
return (
<div>
<h1>I am component A</h1>
</div>
);
}
export default ComponentA;
ComponentB
function ComponentB({ data }) {
function doSomethingWithComponentAData() {
//do stuff with the data from componentA
}
return (
<div>
<h1>I am Component B, I got data: {data.id}</h1>
</div>
);
}
export default ComponentB;
Try this:
// Component A
export const getData = () => { // ... }
// Component B
import { getData } from './ComponentA'
const result = getData()

Dynamic import of react hooks

Can we dynamically import hooks based on the value passed to the component?
For eg.
App.js
<BaseComponent isActive />
BaseComponent.js
if(props.isActive) {
// import useActive (custom Hook)
}
I donot want these(custom hook) files to be imported and increase the size of BaseComponent even when the props contain falsey values.
You can dynamically import hooks as it is just a function (using require), but you shouldn't because you can't use hooks inside conditions.
See Rules of Hooks
Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
If you want conditionally use a hook, use the condition in its implementation (look for example at skip option of useQuery hook from Apollo GraphQL Client).
const useActive = (isUsed) => {
if (isUsed) {
// logic
}
}
You should extract the logic inside the useActive hook and dynamically import it instead of dynamically importing the hook since you should not call Hooks inside loops, conditions, or nested functions., checkout Rules of Hooks:
Let's say your useActive hook was trying to update the document title (in real world, it has to be a big chunk of code that you would consider using dynamic import)
It might be implemented as below:
// useActive.js
import { useEffect } from "react";
export function useActive() {
useEffect(() => {
document.title = "(Active) Hello World!";
}, []);
}
And you tried to use it in the BaseComponent:
// BaseComponent.js
function BaseComponent({ isActive }) {
if (isActive) { // <-- call Hooks inside conditions ❌
import("./useActive").then(({ useActive }) => {
useActive();
});
}
return <p>Base</p>;
}
Here you violated the rule "don't call Hooks inside conditions" and will get an Invalid hook call. error.
So instead of dynamically import the hook, you can extract the logic inside the hook and dynamically import it:
// updateTitle.js
export function updateTitle() {
document.title = "(Active) Hello World!"
}
And you do the isActive check inside the hook:
// BaseComponent.js
function BaseComponent({ isActive }) {
useEffect(() => {
if (!isActive) return;
import("./updateTitle").then(({ updateTitle }) => {
updateTitle();
});
}, [isActive]);
return <p>Base</p>;
}
It works fine without violating any rules of hooks.
I have attached a CodeSandbox for you to play around:
You could create a Higher Order Component that fetches the hook and then passes it down as a prop to a wrapped component. By doing so the wrapped component can use the hook without breaking the rules of hooks, eg from the wrapped component's point of view, the reference to the hook never changes and the hook gets called everytime the wrapped component renders. Here is what the code would look like:
export function withDynamicHook(hookName, importFunc, Component) {
return (props) => {
const [hook, setHook] = useState();
useEffect(() => {
importFunc().then((mod) => setHook(() => mod[hookName]));
}, []);
if (!hook) {
return null;
}
const newProps = { ...props, [hookName]: hook };
return <Component {...newProps} />;
};
}
// example of a Component using it:
const MyComponent = ({useMyHook}) => {
let something = useMyHook();
console.log(something)
return <div>myHook returned something, see the console to inspect it </div>
}
const MyComponentWithHook = withDynamicHook('useMyHook', () => import('module-containing-usemyhook'), MyComponent)
To whoever encountered it as well: You can't use Hooks inside dynamically imported components(however, apparently if you does not use hooks even the first example works):
instead of:
const useDynamicDemoImport = (name) => {
const [comp, setComp] = useState(null);
useEffect(() => {
let resolvedComp = false;
import(`#site/src/demos/${name}`)
.then((m) => {
if (!resolvedComp) {
resolvedComp = true;
setComp(m.default);
}
})
.catch(console.error);
return () => {
resolvedComp = true;
};
}, []);
return comp;
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
{comp}
</Paper>
);
};
export default DemoPreviewer
use React Lazy instead and render the component later
const useDynamicDemoImport = (name) => {
const Comp = React.lazy(() => import(`#site/src/demos/${name}`));
return comp;
};
const RootDemoPreviewer: FC<DemoPreviewerProps> = (props) => {
console.log("RootDemoPreviewer");
return (
<React.Suspense fallback={<div>Loading...</div>}>
<DemoPreviewer {...props} />
</React.Suspense>
);
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
const Comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
<Comp />
</Paper>
);
};
export default RootDemoPreviewer

NextJS routeChangeComplete doesn't trigger on _app.js

Here my ReactJS' snippet:
const LoaderModal = dynamic(
() => import("~/modal/Loader/Loader"),
{ ssr: false }
)
export default class MyApp extends Component {
state={
displayLoader:false
}
componentDidMount(){
let specificChangeStart= Router.events.on('routeChangeStart', () => {
console.log("routeChangeStart")
this.setState({displayLoader:true})
})
let specificComplete = Router.events.on('routeChangeComplete', () => {
console.log("routeChangeComplete")
this.setState({displayLoader:false})
})
let specificError= Router.events.on('routeChangeError',() => {
console.log("routeChangeError")
this.setState({displayLoader:false})
})
}
render(){
let { Component, pageProps }=this.props
let {displayLoader}=this.state
return(
<LayoutContextProvider >
<Component {...pageProps}/>
{
displayLoader &&
<LoaderModal/>
}
</LayoutContextProvider>
)
}
}
I am using nextjs version 9.1.4.
As you can see the Router's events are stored in the componentDidMount() component lifecycle's stage. I have stored them in variable to make them specific from other declarations in my app. I have also tried without assigning them, the both method fail so far.
How can I make the Router works, is there a subtlety to be inform of here?

MobX State Tree async actions and re-rendering React component

I am new to MST and is having a hard time finding more examples with async actions. I have an api that will return different data depending on the params you pass to it. In this case, the api can either return an array of photos or tutorials. I have set up my initial values for the store like so:
data: {
photos: [],
tutorials: []
}
Currently, I am using applySnapshot to update the store and eventually, that will trigger a re-render of my React component. In order to display both photos and tutorials, I need to call the api twice (Once with the params for photos and the second time for tutorials). I am running into an issue where the snapshot from the first update shows that photos and tutorials have the same values and only on the second update, do I get the correct values. I am probably misusing applySnapshot to re-render my React components. I would like to know the better/proper way of doing this. What is the best way to re-render my React components after the api has yielded a repsonse. Any suggestions are much appreciated
I have set up my store like this:
import { RootModel } from '.';
import { onSnapshot, getSnapshot, applySnapshot } from 'mobx-state-tree';
export const setupRootStore = () => {
const rootTree = RootModel.create({
data: {
photos: [],
tutorials: []
}
});
// on snapshot listener
onSnapshot(rootTree, snapshot => console.log('snapshot: ', snapshot));
return { rootTree };
};
I have created the following model with an async action using generators:
import {types,Instance,applySnapshot,flow,onSnapshot} from 'mobx-state-tree';
const TestModel = types
.model('Test', {
photos: types.array(Results),
tutorials: types.array(Results)
})
.actions(self => ({
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
applySnapshot(self, {
...self,
photos: [... results, ...self.photos],
tutorials: [... results, ...self.tutorials]
});
})
}))
.views(self => ({
getPhoto() {
return self.photos;
},
getTutorials() {
return self.tutorials;
}
}));
const RootModel = types.model('Root', {
data: TestModel
});
export { RootModel };
export type Root = Instance<typeof RootModel>;
export type Test = Instance<typeof TestModel>;
React component for Photos.tsx
import React, { Component } from 'react';
import Spinner from 'components/Spinner';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root
}
#inject('rootTree')
#observer
class Photos extends Component<Props> {
componentDidMount() {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
}
displayPhoto() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const photoResults = rootTree.data.getPhoto();
if (photoResults.$treenode.snapshot[0]) {
return (
<div>
<div className='photo-title'>{'Photo'}</div>
{photoResults.$treenode.snapshot.map(Item => (
<a href={photoItem.attributes.openUrl} target='_blank'>
<img src={photoItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='photo-module'>{this.displayPhoto()}</div>;
}
}
export default Photos;
Similarly, Tutorials.tsx is like so:
import React, { Component } from 'react';
import Spinner from '';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root;
}
#inject('rootTree')
#observer
class Tutorials extends Component<Props> {
componentDidMount() {
if (this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('tuts');
}
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.search.fetchData('tuts');
}
}
displayTutorials() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const tutResults = rootTree.data.getTutorials();
if (tutResults.$treenode.snapshot[0]) {
return (
<div>
<div className='tutorials-title'>{'Tutorials'}</div>
{tutResults.$treenode.snapshot.map(tutorialItem => (
<a href={tutorialItem.attributes.openUrl} target='_blank'>
<img src={tutorialItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='tutorials-module'>{this.displayTutorials()}</div>;
}
}
export default Tutorials;
Why are you using applySnapshot at all in this case? I don't think it's necessary. Just assign your data as needed in your action:
.actions(self => ({
//If you're fetching both at the same time
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
//you need cast() if using Typescript otherwise I think it's optional
self.photos = cast([...results.photos, ...self.photos])
//do you really intend to prepend the results to the existing array or do you want to overwrite it with the sever response?
self.tutorials = cast(results.tutorials)
})
}))
Or if you need to make two separate requests to fetch your data it's probably best to make it two different actions
.actions(self => ({
fetchPhotos: flow(function* fetchPhotos(param) {
const results = yield api.fetch(param)
self.photos = cast([... results, ...self.photos])
}),
fetchTutorials: flow(function* fetchTutorials(param) {
const results = yield api.fetch(param)
self.tutorials = cast([... results, ...self.tutorials])
}),
}))
Regardless, it doesn't seem like you need applySnapshot. Just assign your data in your actions as necessary. There's nothing special about assigning data in an async action.

Cannot Persist Store Connected Components - React-Redux Tests

I cannot figure out why this is not working:
-spec.js
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
},
store = fakeStore(state),
container = <HomePageContainer store={store} />;
const homePageContainer = shallow(container),
homePage = homePageContainer.find(HomePage);
expect(homePage.props.company.name).to.equal(state.company.name)
});
const fakeStore = state => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
HomePageContainer.js
import React from 'react';
import { connect } from 'react-redux'
import HomePage from '../../client/components/HomePage';
export const mapStateToProps = state => {
company: state.company
}
export { HomePage }
export default connect(mapStateToProps)(HomePage);
HomePage.js
import React, { Component, PropTypes } from 'react';
export default class HomePage extends Component {
render(){
return (
<div className='homepage'>
{/*{this.props.company.name}*/}
</div>
)
}
}
I'm getting this type error because props.company is undefined so for some reason it's not persisting state to :
TypeError: Cannot read property 'name' of undefined
That error is relating to the assert when it's trying to read expect(homePage.props.company.name)
I notice that when putting a breakpoint inside mapStateToProps, that it's not picking up the state object from the store still for some reason:
I know you can pass just the store via props...and if there's nothing in the context, connect() will be able to find it via the store prop. For example in another test suite, this passes just fine:
it('shallow render container and dive into child', () => {
const container = shallow(<ExampleContainer store={fakeStore({})}/>);
expect(container.find(Example).dive().text()).to.equal('Andy');
});
Problem
At this line you pass the store as a prop into <HomePageContainer>:
// ...
container = <HomePageContainer store={store} />;
// ...
But connect() needs the store via context.
And you must wrap the object you want to return in mapStateToProps in parenthese.
Solution
You can use shallow() to make the store available as a context property.
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
};
const store = fakeStore(state);
const homePageContainer = shallow(
<HomePageContainer />,
// Make the store available via context
{ context: { store } }
);
const homePage = homePageContainer.find(HomePage);
expect(homePage.props().company.name).to.equal(state.company.name)
});
Updated mapStateToProps:
export const mapStateToProps = state => ({
company: state.company
});
Found out the problem was really my helper.
This was passing a object with property "state" in it. Not what I want. That would have meant that mapStateToProps would have had to reference the props with state.state.somePropName
const fakeStore = (state) => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
changed it to this, and now mapStateToProps is working fine, it's able to access it with the props as the root level of the object literal, the object I passed in from my test:
const fakeStore = (state) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
});

Categories

Resources