How to setState of redux wrapped component? - jestjs

I'm trying to test a component that is wrapped in Redux:
beforeEach(async () => {
component = await mount(
<Provider store={buildStore()}>
<CheckoutOverlay cartItems={basicCart} />
</Provider>
)
await component.update();
})
Things I've tried:
component.find(CheckoutOverlay).instance().state.coupon={} (Cannot read 'state' of null)
component.find(CheckoutOverlay).state.coupon={mode:0, value:.25}; (state does not update)
component.find(CheckoutOverlay).setState({coupon: {mode: 0, value: .25}}); (setState() can
only be called on class components)
component.find(CheckoutOverlay).instance().setState({coupon: {mode: 0, value: .25}}); (cannot read property 'setState' of null)
I've also tried some suggestions that use SHALLOW() and .dive(). But I always get the error 'dive()' is not a function. I am using Enzyme 3.11.0
How can I update the state of a component wrapped in the Redux Provider?
Additional Info:
Inside my component, CheckoutOverlay, I have another component that calls a function in CheckoutOverlay. This function stores the data sent in the State.
I am trying to simulate what happens when the data is sent to CheckoutOverlay.
The sub component has tests, and, I suppose, I can do something like simulate the click on that component. But that seems like it is more involved than it should be.

Since you have not mentioned what you are trying to test, I am giving you some general suggestions.
You should not test the implementation details, rather test the functionality.
If your state is dependant on the redux store state, you can pass the required data as initialState to your provider.
You cannot use instance on a functional component

Related

Unable to pull through API to react frontend with

I am struggling to pull through the api to the front end. I completed it successfully with
https://jsonplaceholder.typicode.com/ Just mapped out the arrays. i am struggling however to pull through this seperate api I wanted to use
https://gateway.marvel.com/v1/public/comics?apikey=xxxxxxxxxxxxxxxx&ts=redant&hash=140e85a50884cef76d614f6dacada288
the erro is..
"Uncaught TypeError: Cannot read properties of undefined (reading 'results')"
so clearly it isnt actually able to get hold of results
What am I doing wrong?
import React, {Component} from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
list: []
};
}
componentDidMount() {
fetch('https://gateway.marvel.com/v1/public/comics?apikey=3cb62d086d5debdeea139095cbb07fe4&ts=redant&hash=140e85a50884cef76d614f6dacada288')
.then (response => response.json())
.then(users => this.setState({list:users}))
}
render() {
return (
<div className='App'>
{
this.state.list.data.results.map(result =>
<h1 key={result.id}>{result.urls}</h1>
)
}
</div>
)
}
}
export default App
The error Uncaught TypeError: Cannot read properties of undefined (reading 'results') in your code means that this.state.list.data returns as undefined. This means that you'll need to focus on your state property list to ensure that it has a data property. As we see in your constructor, data is initialized to an empty array which does not contain the data property.
Something we can do to prevent the error is to surround your code with an undefined check:
if (this.state.list.data != undefined) {
this.state.list.data.results.map(result =>
<h1 key={result.id}>{result.urls}</h1>
)
}
At this point, though, we don't know if your API call is returning good data or not since your program throws the error before that (since the fetch and setState are asynchronous), so the code above mainly addresses the error that you're getting rather than focusing on the "pull through the api to the front end" portion of your question.
Here's what you can do inside your then part after fetching the results
this.setState({list:users.data.results}
And inside the map function, do the following:
this.state.list.map(result =>
<h1 key={result.id}>{result.urls}</h1>
You are getting an error because List is initially empty it doesn't have any key called as results, once you fetch the result only then can you loop through your result.
Another solution would be to simply add a loader, you can control that via state as well.

ESLint for React: is there a rule to detect props that need a useMemo?

With react, in the JSX code returned by a functional component I can have:
MyComponent.jsx
<MySubComponent props1={{a: 0, b:1}}/>
OR
<MySubComponent props2={["toto", "tata"]}/>
Where my sub component is exported with memoization:
MySubComponent.jsx
export default React.memo(MySubComponent)
However this breaks memoization, because props with the format {{...}} or {[...]} will instanciate a new object/array each time (same value, but different memory address), thus props shallow-compare by React.memo sees it different.
Is there any ESLint/JSLint rule to detect those kinds of props ?
You can use useMemo to memoize the object or array and pass it as props to MySubComponent like this:
const obj = React.useMemo(() => ({a: 0, b:1}), []);
<MySubComponent props1={obj}/>
In SubComponent.jsx
export default React.memo(MySubComponent);
This only works for simple objects.

How to pass nested data structures as properties in LitElement?

In a parent component I have something like:
render() => {
const data = {a:1,b:[1,2,3]}; // of course this is a simplified version of the code
return html`<child-component data=${data}></child-component>`
}
Which is basically equivalent to:
render() => {
const data = {a:1,b:[1,2,3]}; // of course this is a simplified version of the code
return html`<child-component data="[object Object]"></child-component>`
}
Which is basically useless...
Is there a simple way to pass complex object hierarchies into litElement components?
As far as I can tell, my options are:
Option 1. Use attributes: I'm a bit of a litElement noob so I'm not sure if this will work and I'm not sure how to make it work without having to make extra function calls. It would be nice if I could just do all the necessary work inside html.
Research in progress.
Option 2. Use Json.
Stringify the object in the parent component
render() => {
const data = {a:1,b:[1,2,3]}; // of course this is a simplified version of the code
return html`<child-component data=${JSON.stringify(data)}></child-component>`
}
then parse the json in the child component.
This just seems a bit inelegant to me though.
But it works.
In this case what you probably want is to pass the object as a property rather than as an attribute. For complex data such as objects, arrays, functions, etc. that's the preferred method.
You can do it with the following syntax:
render() => {
const data = {a:1,b:[1,2,3]};
// note the period (.), that's the token used to identify that you're passing data as a property
return html`<child-component .data=${data}></child-component>`
}
In general, you should probably give Lit's templating guide a read as some of the most common use cases are covered throughout it.

Jest error with <Trans>: You forgot to export your component from the file it's defined in, or you might have mixed up default and named imports

Error: Uncaught [Error: Element type is invalid: expected a string
(for built-in components) or a class/function (for composite
components) but got: undefined. You likely forgot to export your
component from the file it's defined in, or you might have mixed up
default and named imports.
This is the error I was getting while running test in jest. React component which is being tested uses <Trans> from react-i18next. When I comment that portion of code, test were working as expected.
The error shown is very very very miss leading.
In my case it was missing mock for <Trans>. While I had mock for react-i18next, but since I had many components to cover with tests, and some of them were using <Trans> and some of them not, I copy/paste test files but totally forgot to check about mock. It took me few hours to notice it, after I replaced <Trans> to text like <Typography> from material-ui...
jest.mock('react-i18next', () => ({
withTranslation: () => (Component: any) => {
Component.defaultProps = {...Component.defaultProps, t: (children: any) => children};
return Component;
},
Trans: ({children}: any) => children, // this line was missing (() => jest.fn() might also work)
}));
Hope it will save some time for some of you :)
I faced the same issue, in order to resolve the issue I mocked the Trans component like this
jest.mock("react-i18next", () => ({
Trans: ({ i18nKey }: { i18nKey: string }) => i18nKey,
}));
Instead of passing the node, we can simply pass the i18nKey.
In my case, I am only checking the key value. Hope it helps!

How can useEffect WITH [] argument run multiple times

In what cases can such a structure
useEffect(() => {
...
}, []);
run multiple times?
I thought it shouldn't by definition, but on this video it does:
https://www.youtube.com/watch?v=7RMwZ0_tANg
[] means, that it will render only on initial render, so probably there is multiple rendering of a component
The way you used useEffect it is working as equality to componentDidMount for class component.
useEffect can be used as componentDidMount , componentDidUpdate and componentWillUnmount.
useEffect(() => {
console.log('mounted'); //This way you can get componentDidMount
return () => console.log('unmounting...'); //This way you can get componentDidUnmount
}, []) // <-- The effect depends on variable you put into array(if you would want to check and do something every time variable did update you would put variable name inside - componentDidUpdate)
I found out, how that was possible.
const ComponentWithLoader = WithLoader(Component);
return <ComponentWithLoader { ...{
isLoading, data, remove, update, setData, ...props,
}}/>;
I used a higher-order component inside the body of a functional component. And since WithLoader call created a completely new component every time, the whole component structure inside it was recreated on every rerender of the outer functional component.

Resources