How can I test if a prop is passed to child? - reactjs

My component looks something like this: (It has more functionality as well as columns, but I have not included that to make the example simpler)
const WeatherReport: FunctionComponent<Props> = ({ cityWeatherCollection, loading, rerender }) => {
/* some use effects skipped */
/* some event handlers skipped */
const columns = React.useMemo(() => [
{
header: 'City',
cell: ({ name, title }: EnhancedCityWeather) => <Link to={`/${name}`} className="city">{title}</Link>
},
{
header: 'Temp',
cell: ({ temperature }: EnhancedCityWeather) => (
<div className="temperature">
<span className="celcius">{`${temperature}°C`}</span>
<span className="fahrenheit">{` (~${Math.round(temperature * (9 / 5)) + 32}°F)`}</span>
</div>
)
},
{
header: '',
cell: ({ isFavorite } : EnhancedCityWeather) => isFavorite && (
<HeartIcon
fill="#6d3fdf"
height={20}
width={20}
/>
),
},
], []);
return (
<Table columns={columns} items={sortedItems} loading={loading} />
);
};
Now, I wrote some tests like this:
jest.mock('../../../components/Table', () => ({
__esModule: true,
default: jest.fn(() => <div data-testid="Table" />),
}));
let cityWeatherCollection: EnhancedCityWeather[];
let loading: boolean;
let rerender: () => {};
beforeEach(() => {
cityWeatherCollection = [/*...some objects...*/];
loading = true;
rerender = jest.fn();
render(
<BrowserRouter>
<WeatherReport
cityWeatherCollection={cityWeatherCollection}
loading={loading}
rerender={rerender}
/>
</BrowserRouter>
);
});
it('renders a Table', () => {
expect(screen.queryByTestId('Table')).toBeInTheDocument();
});
it('passes loading prop to Table', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({ loading }),
expect.anything(),
);
});
it('passes items prop to Table after sorting by isFavorite and then alphabetically', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({
items: cityWeatherCollection.sort((item1, item2) => (
+item2.isFavorite - +item1.isFavorite
|| item1.name.localeCompare(item2.name)
)),
}),
expect.anything(),
);
});
If you check my component, it has a variable called columns. I am assigning that variable to Table component.
I think, I should test that columns are being passed as props to the Table component. Am I thinking right? If so, can you please tell me how can I write a test case for that?
Also, it will be helpful if you can suggest me how can i test each cell declared inside columns property.

It is not recommended to test implementation details, such as component props, with React Testing Library. Instead you should be asserting on the screen content.
Recommended
expect(await screen.findByText('some city')).toBeInTheDocument();
expect(screen.queryByText('filtered out city')).not.toBeInTheDocument();
Not Recommended
If you want to test props anyways, you can try the sample code below. Source
import Table from './Table'
jest.mock('./Table', () => jest.fn(() => null))
// ... in your test
expect(Table).toHaveBeenCalledWith(props, context)
You might consider this approach mainly on the two following scenarios.
You already tried the recommended approach but you noticed the component is:
using legacy code and because of that it makes testing very hard. Refactoring the component would also take too long or be too risky.
is very slow and it drastically increases the testing time. The component is also already tested somewhere else.
have a look at a very similar question here

You can use the props() method, doing something like this:
expect(Table.props().propYouWantToCheck).toBeFalsy();
Just doing your component.props() then the prop you want, you can make any assert with it.

Related

Is there a good way to use React hooks inside a callback or render prop?

Often times I find myself creating or using components where callbacks are used to create part of the component tree, as in these two simplified examples:
// Example 1: Render an array of components
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <li key={item.id} style={{ paddingTop: item.paddingTop }}>{item.label}</li>)}
</ul>
);
// Example 2: Use render props
const ExampleComponent2 = () => (
<Dialog
renderHeader={useCallback((dialogCtx) => 'Header', [])}
renderBody={useCallback((dialogCtx) => <button onClick={() => dialogCtx.closeDialog(null)}>Test</button>, [])}
/>
);
As you can see, the callback results in both cases are dependent on the callback argument. In order to avoid a new style object (in example 1) and a new onClick function (in example 2) to be created on every render, I should wrap them in useMemo/useCallback, but that is not possible here because they are created inside the callbacks rather than on the root level of the component.
The only way that I'm aware of to work around this problem is to create designated components for each callback:
// Example 1: Render an array of components
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <ExampleComponent1Item key={item.id} item={item}/>)}
</ul>
);
const ExampleComponent1Item = ({ item }) => <li style={useMemo(() => ({ paddingTop: item.paddingTop }), [item.paddingTop])}>{item.label}</li>;
// Example 2: Use render props
const ExampleComponent2 = () => (
<Dialog
renderHeader={useCallback((dialogCtx) => <ExampleComponent2Header dialogCtx={dialogCtx}/>, [])}
renderBody={useCallback((dialogCtx) => <ExampleComponent2Body dialogCtx={dialogCtx}/>, [])}
/>
);
const ExampleComponent2Header = ({ dialogCtx }) => 'Header';
const ExampleComponent2Body = ({ dialogCtx }) => <button onClick={useCallback(() => dialogCtx.closeDialog(null), [dialogCtx.closeDialog])}>Test</button>;
You can already see in this simplified example how splitting up the app in such a way creates a lot of additional code and makes the app much harder to read. In more complex scenarios where a lot of props from the parent component need to be reused in the sub component, the sub components will become even more bulky, particularly when prop types need to be defined as well.
It seems to me that both of these example are rather common use cases, since callbacks are the only way to generate a list of components from an array in React, and render props are a common pattern in more complex components. I'm wondering:
Is there any way how I could write the above example without splitting off the callback results into separate components? For example some way to use hooks directly inside the callbacks.
Is there an alternative pattern for mapping an array to a list of components that doesn't rely on callbacks, so hooks can be used?
Is there an alternative pattern to render props that makes it easier to use hooks?
Update: To make it clear, I’m looking for a programming pattern, not specific problems in the simplified example code above.
I don't understand what's wrong with example 1.
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <li key={item.id} style={{ paddingTop: item.paddingTop }}>{item.label}</li>)}
</ul>
);
This is perfectly fine in React. Until you can see that the style prop is causing performance issues, you shouldn't try to optimize it.
Version with hooks, in case you somehow really need it:
const ExampleComponent1Item = ({ item }) => {
const style = useMemo(() => ({ paddingTop: item.paddingTop }), [item.paddingTop]);
return (<li style={style}>{item.label}</li>);
};
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <ExampleComponent1Item key={item.id} item={item}/>)}
</ul>
);
The same goes for example 2:
const ExampleComponent2 = () => {
const renderHeader = useCallback((dialogCtx) => 'Header', []);
const renderBody = useCallback((dialogCtx) => <button onClick={() => dialogCtx.closeDialog(null)}>Test</button>, []);
return (<Dialog renderHeader={renderHeader} renderBody=renderBody}/>);
};
If you want to avoid the onClick function to be recreated each time renderBody is called, you can do:
const ExampleComponent2Body = {( dialogCtx }) => {
const onClick = useCallback(() => dialogCtx.closeDialog(null), [dialogCtx.closeDialog]);
return (<button onClick={onClick}>Test</button>);
};
const ExampleComponent2 = () => {
const renderHeader = useCallback((dialogCtx) => 'Header', []);
const renderBody = useCallback((dialogCtx) => <ExampleComponent2Body dialogCtx={dialogCtx}/>, []);
return (<Dialog renderHeader={renderHeader} renderBody=renderBody}/>);
};

how to test that props are passed to child component with react testing library and jest? [duplicate]

My component looks something like this: (It has more functionality as well as columns, but I have not included that to make the example simpler)
const WeatherReport: FunctionComponent<Props> = ({ cityWeatherCollection, loading, rerender }) => {
/* some use effects skipped */
/* some event handlers skipped */
const columns = React.useMemo(() => [
{
header: 'City',
cell: ({ name, title }: EnhancedCityWeather) => <Link to={`/${name}`} className="city">{title}</Link>
},
{
header: 'Temp',
cell: ({ temperature }: EnhancedCityWeather) => (
<div className="temperature">
<span className="celcius">{`${temperature}°C`}</span>
<span className="fahrenheit">{` (~${Math.round(temperature * (9 / 5)) + 32}°F)`}</span>
</div>
)
},
{
header: '',
cell: ({ isFavorite } : EnhancedCityWeather) => isFavorite && (
<HeartIcon
fill="#6d3fdf"
height={20}
width={20}
/>
),
},
], []);
return (
<Table columns={columns} items={sortedItems} loading={loading} />
);
};
Now, I wrote some tests like this:
jest.mock('../../../components/Table', () => ({
__esModule: true,
default: jest.fn(() => <div data-testid="Table" />),
}));
let cityWeatherCollection: EnhancedCityWeather[];
let loading: boolean;
let rerender: () => {};
beforeEach(() => {
cityWeatherCollection = [/*...some objects...*/];
loading = true;
rerender = jest.fn();
render(
<BrowserRouter>
<WeatherReport
cityWeatherCollection={cityWeatherCollection}
loading={loading}
rerender={rerender}
/>
</BrowserRouter>
);
});
it('renders a Table', () => {
expect(screen.queryByTestId('Table')).toBeInTheDocument();
});
it('passes loading prop to Table', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({ loading }),
expect.anything(),
);
});
it('passes items prop to Table after sorting by isFavorite and then alphabetically', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({
items: cityWeatherCollection.sort((item1, item2) => (
+item2.isFavorite - +item1.isFavorite
|| item1.name.localeCompare(item2.name)
)),
}),
expect.anything(),
);
});
If you check my component, it has a variable called columns. I am assigning that variable to Table component.
I think, I should test that columns are being passed as props to the Table component. Am I thinking right? If so, can you please tell me how can I write a test case for that?
Also, it will be helpful if you can suggest me how can i test each cell declared inside columns property.
It is not recommended to test implementation details, such as component props, with React Testing Library. Instead you should be asserting on the screen content.
Recommended
expect(await screen.findByText('some city')).toBeInTheDocument();
expect(screen.queryByText('filtered out city')).not.toBeInTheDocument();
Not Recommended
If you want to test props anyways, you can try the sample code below. Source
import Table from './Table'
jest.mock('./Table', () => jest.fn(() => null))
// ... in your test
expect(Table).toHaveBeenCalledWith(props, context)
You might consider this approach mainly on the two following scenarios.
You already tried the recommended approach but you noticed the component is:
using legacy code and because of that it makes testing very hard. Refactoring the component would also take too long or be too risky.
is very slow and it drastically increases the testing time. The component is also already tested somewhere else.
have a look at a very similar question here
You can use the props() method, doing something like this:
expect(Table.props().propYouWantToCheck).toBeFalsy();
Just doing your component.props() then the prop you want, you can make any assert with it.

How to make child component reactive to state change in mobx using mobx-react-lite

I'm using mobx-state-tree and mobx-react-lite, can someone guide me to a better pattern,
wishlist.js - wishlist store
import { types } from 'mobx-state-tree'
export const WishListItem = types.model('WishListItem', {
name: types.string,
price: types.number,
image: "",
}).actions(self => ({
changeName(newName) {
self.name = newName
},
}))
export const WishList = types.model('WishList', {
items: types.optional(types.array(WishListItem), []),
})
root.js - root store
export const RootStore = types.model('RootStore', {
counter: types.optional(Counter, { count: 0 }),
wishList: types.optional(WishList, {
items: [{ image: '', price: 10, name: 'Yoda' }]
}),
})
I'm updating the store as
setInterval(() => store.wishList.items[0].changePrice(Math.random() * 100), 500)
In my Wishlist view
wishlist.jsx
const WishListItem = ({ image, name, price }) => {
return useObserver(
() =>
<div>
<img src={image} />
<h3>{name}</h3>
<h5>{price}</h5>
</div>
)
}
const WishListView = ({ items }) => {
return useObserver(
() => <>
{
items.map(
(item, key) => <WishListItem {...item} key={key} />
)
}
</>
)
}
export default () => useObserver(() => (
<WishListView items={store.wishList.items} />
))
Here I have to use useObserver or Observer at every level of the component tree, to make it reactive, is there any way to pass a reactive reference to the child?
It works perfectly fine with primitive types like string or number, but with an array or an object, I have to either directly refer changing variables at the parent like store.wishList[0].price or use useObserver in the whole tree.
I want to pass the items array to children, and update children on the changes, just this at the root
export default () => useObserver(() => (
<WishListView items={store.wishList.items} />
))
and no more useObserver at it's childrens
Update
A workaround I found was to destructure the array, now the changes are reactive since we are directly accessing the variables that are changing.
export default () => useObserver(() => {
const items = store.wishList.items.map(item => ({ ...item }))
return <WishListView items={items} />
})
and no more useObserver at it's childrens
It is actually better to mark all components as observer if possible. For example, if you mark each Item as observer and one of the items change its name then only this component will rerender. If you dont make Item observer then your whole List will rerender which is quite bad if have lots of items or deep DOM tree. Also it does not make sense to rerender whole list when just one item changes.
Look here for explanation https://mobx.js.org/refguide/observer-component.html#when-to-apply-observer
So your workaround is a bad pratice and should be used only as last resort if you dont have control over children components and can't make them observer.

Refactoring class component to functional component with hooks, getting Uncaught TypeError: func.apply is not a function

This is my first attempt to refactor code from a class component to a functional component using React hooks. The reason we're refactoring is that the component currently uses the soon-to-be-defunct componentWillReceiveProps lifecylcle method, and we haven't been able to make the other lifecycle methods work the way we want. For background, the original component had the aforementioned cWRP lifecycle method, a handleChange function, was using connect and mapStateToProps, and is linking to a repository of tableau dashboards via the tableau API. I am also breaking the component, which had four distinct features, into their own components. The code I'm having issues with is this:
const Parameter = (props) => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
let parameterSelections = parameterCurrent;
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [props.parameterCurrent])
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
console.log(parameterCurrent[key]);
return (
prevState => ({
...prevState,
parameterSelections: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
Swal.fire({
position: "center",
icon: "success",
title:
JSON.stringify(key) + " set to " + JSON.stringify(valKey),
font: "1em",
showConfirmButton: false,
timer: 2500,
heightAuto: false,
height: "20px"
});
})
.otherwise(function (err) {
alert(
Swal.fire({
position: "top-end",
icon: "error",
title: err,
showConfirmButton: false,
timer: 1500,
width: "16rem",
height: "5rem"
})
);
});
}
);
};
const classes = useStyles();
return (
<div>
{Object.keys(parameterSelect).map((key, index) => {
return (
<div>
<FormControl component="fieldset">
<FormLabel className={classes.label} component="legend">
{key}
</FormLabel>
{parameterSelect[key].map((valKey, valIndex) => {
console.log(parameterSelections[key])
return (
<RadioGroup
aria-label="parameter"
name="parameter"
value={parameterSelections[key]}
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
<FormControlLabel
className={classes.formControlparams}
value={valKey}
control={
<Radio
icon={
<RadioButtonUncheckedIcon fontSize="small" />
}
className={clsx(
classes.icon,
classes.checkedIcon
)}
/>
}
label={valKey}
/>
</RadioGroup>
);
})}
</FormControl>
<Divider className={classes.divider} />
</div>
);
})
}
</div >
)};
export default Parameter;
The classes const is defined separately, and all imports of reducers, etc. have been completed. parameterSelect in the code points to all available parameters, while parameterCurrent points to the default parameters chosen in the dashboard (i.e. what the viz initially loads with).
Two things are happening: 1. Everything loads fine on initial vizualization, and when I click on the Radio Button to change the parameter, I can see it update on the dashboard - however, it's not actually showing the radio button as being selected (it still shows whichever parameter the viz initialized with as being selected). 2. When I click outside of the Filterbar (where this component is imported to), I get Uncaught TypeError: func.apply is not a function. I refactored another component and didn't have this issue, and I can't seem to determine if I coded incorrectly in the useEffect hook, the handleParameterChange function, or somewhere in the return statement. Any help is greatly appreciated by this newbie!!!
This is a lot of code to take in without seeing the original class or having a code sandbox to load up. My initial thought is it might be your useEffect
In your refactored code, you tell your useEffect to only re-run when the props.parameterCurrent changes. However inside the useEffect you don't make use of props.parameterCurrent, you instead make use of parameterCurrent from the local lexical scope. General rule of thumb, any values used in the calculations inside a useEffect should be in the list of re-run dependencies.
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [parameterCurrent])
However, this useEffect doesn't seem to do anything, so while its dependency list is incorrect, I don't think it'll solve the problem you are describing.
I would look at your dispatch and selector. Double check that the redux store is being updated as expected, and that the new value is making it from the change callback, to the store, and back down without being lost due to improper nesting, bad key names, etc...
I'd recommend posting a CodeSandbox.io link or the original class for further help debugging.

useLoopCallback -- useCallback hook for components created inside a loop

I'd like to start a discussion on the recommended approach for creating callbacks that take in a parameter from a component created inside a loop.
For example, if I'm populating a list of items that will have a "Delete" button, I want the "onDeleteItem" callback to know the index of the item to delete. So something like this:
const onDeleteItem = useCallback(index => () => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
return (
<div>
{list.map((item, index) =>
<div>
<span>{item}</span>
<button type="button" onClick={onDeleteItem(index)}>Delete</button>
</div>
)}
</div>
);
But the problem with this is that onDeleteItem will always return a new function to the onClick handler, causing the button to be re-rendered, even when the list hasn't changed. So it defeats the purpose of useCallback.
I came up with my own hook, which I called useLoopCallback, that solves the problem by memoizing the main callback along with a Map of loop params to their own callback:
import React, {useCallback, useMemo} from "react";
export function useLoopCallback(code, dependencies) {
const callback = useCallback(code, dependencies);
const loopCallbacks = useMemo(() => ({map: new Map(), callback}), [callback]);
return useCallback(loopParam => {
let loopCallback = loopCallbacks.map.get(loopParam);
if (!loopCallback) {
loopCallback = (...otherParams) => loopCallbacks.callback(loopParam, ...otherParams);
loopCallbacks.map.set(loopParam, loopCallback);
}
return loopCallback;
}, [callback]);
}
So now the above handler looks like this:
const onDeleteItem = useLoopCallback(index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
This works fine but now I'm wondering if this extra logic is really making things faster or just adding unnecessary overhead. Can anyone please provide some insight?
EDIT:
An alternative to the above is to wrap the list items inside their own component. So something like this:
function ListItem({key, item, onDeleteItem}) {
const onDelete = useCallback(() => {
onDeleteItem(key);
}, [onDeleteItem, key]);
return (
<div>
<span>{item}</span>
<button type="button" onClick={onDelete}>Delete</button>
</div>
);
}
export default function List(...) {
...
const onDeleteItem = useCallback(index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
return (
<div>
{list.map((item, index) =>
<ListItem key={index} item={item} onDeleteItem={onDeleteItem} />
)}
</div>
);
}
Performance optimizations always come with a cost. Sometimes this cost is lower than the operation to be optimized, sometimes is higher. useCallback it's a hook very similar to useMemo, actually you can think of it as a specialization of useMemo that can only be used in functions. For example, the bellow statements are equivalents
const callback = value => value * 2
const memoizedCb = useCallback(callback, [])
const memoizedWithUseMemo = useMemo(() => callback, [])
So for now on every assertion about useCallback can be applied to useMemo.
The gist of memoization is to keep copies of old values to return in the event we get the same dependencies, this can be great when you have something that is expensive to compute. Take a look at the following code
const Component = ({ items }) =>{
const array = items.map(x => x*2)
}
Uppon every render the const array will be created as a result of a map performed in items. So you can feel tempted to do the following
const Component = ({ items }) =>{
const array = useMemo(() => items.map(x => x*2), [items])
}
Now items.map(x => x*2) will only be executed when items change, but is it worth? The short answer is no. The performance gained by doing this is trivial and sometimes will be more expensive to use memoization than just execute the function each render. Both hooks(useCallback and useMemo) are useful in two distinct use cases:
Referencial equality
When you need to ensure that a reference type will not trigger a re render just for failing a shallow comparison
Computationally expensive operations(only useMemo)
Something like this
const serializedValue = {item: props.item.map(x => ({...x, override: x ? y : z}))}
Now you have a reason to memoized the operation and lazily retrieve the serializedValue everytime props.item changes:
const serializedValue = useMemo(() => ({item: props.item.map(x => ({...x, override: x ? y : z}))}), [props.item])
Any other use case is almost always worth to just re compute all values again, React it's pretty efficient and aditional renders almost never cause performance issues. Keep in mind that sometimes your efforts to optimize your code can go the other way and generate a lot of extra/unecessary code, that won't generate so much benefits (sometimes will only cause more problems).
The List component manages it's own state (list) the delete functions depends on this list being available in it's closure. So when the list changes the delete function must change.
With redux this would not be a problem because deleting items would be accomplished by dispatching an action and will be changed by a reducer that is always the same function.
React happens to have a useReducer hook that you can use:
import React, { useMemo, useReducer, memo } from 'react';
const Item = props => {
//calling remove will dispatch {type:'REMOVE', payload:{id}}
//no arguments are needed
const { remove } = props;
console.log('component render', props);
return (
<div>
<div>{JSON.stringify(props)}</div>
<div>
<button onClick={remove}>REMOVE</button>
</div>
</div>
);
};
//wrap in React.memo so when props don't change
// the ItemContainer will not re render (pure component)
const ItemContainer = memo(props => {
console.log('in the item container');
//dispatch passed by parent use it to dispatch an action
const { dispatch, id } = props;
const remove = () =>
dispatch({
type: 'REMOVE',
payload: { id },
});
return <Item {...props} remove={remove} />;
});
const initialState = [{ id: 1 }, { id: 2 }, { id: 3 }];
//Reducer is static it doesn't need list to be in it's
// scope through closure
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
//remove the id from the list
return state.filter(
item => item.id !== action.payload.id
);
}
return state;
};
export default () => {
//initialize state and reducer
const [list, dispatch] = useReducer(
reducer,
initialState
);
console.log('parent render', list);
return (
<div>
{list.map(({ id }) => (
<ItemContainer
key={id}
id={id}
dispatch={dispatch}
/>
))}
</div>
);
};

Resources