I'm trying to mock a functional component
jest.mock('./loadingStack/LoadingStack.tsx', () =>
jest.fn().mockReturnValue(null),
);
and then manipulate the mocked component in my test
test('should render LoadingStack by default', async () => {
(LoadingStack as jest.Mock).mockReturnValueOnce(
<View testID="mock-loading-stack" />,
);
const wrapper = renderWithRedux(
<NavigationContainer>
<MainStack />
</NavigationContainer>,
null,
);
await waitFor(() => wrapper.getByTestId('mock-loading-stack'));
});
My test is passing but I get this warning:
Got a component with the name 'mockConstructor' for the screen 'LoadingStack'. React Components must start with an uppercase letter. If you're passing a regular function and not a component, pass it as children to 'Screen' instead. Otherwise capitalize your component's name.
Do I miss something here?
Related
In the following code, I expect OfferList to rerender when I add an offer item to the store. OfferList itself is not an observable, but the offer array is passed as a prop.
export const MerchantScreen: FC = observer(() => {
const { merchantStore } = useStores()
return (
<View>
<OfferList data={merchantStore.offers} />
<View>
<Button title={"New Offer"} onPress={() => merchantStore.addOffer()}/>
</View>
</View>
)
})
export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
const renderItem = (offer: ListRenderItemInfo<any>) => {
return (
<Text>{offer.name}</Text>
)
}
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
)
}
I use Mobx State Tree. All merchantStore.addOffer() does for now is push another offer item into the array.
What I tried / findings:
When I read from the store in my MerchantScreen, e.g. by adding
<Text>{ merchantStore.offers.toString() }</Text>
, the OfferList will also update. I suspect that reading from the store directly in the parent component will force a rerender of the child component as well.
I stumbled upon some answers here that would indicate that a missing key attribute within the FlatList renderItems could be the issue. Tried using key={item.id} to no avail. Also, as you can see I use the keyExtractor prop of FlatList.
Another answers suggested introducing local state to the component like this:
export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
const [offers, setOfferss] = useState()
useEffect(() => {
setOffers(data)
}, [data])
const renderItem = (offer: ListRenderItemInfo<any>) => {
return (
<Text>{offer.name}</Text>
)
}
return (
<FlatList
data={offers}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
)
}
This does not work and my gutfeeling is that this is not how it's done.
As you see my MerchantScreen parent component is an observable while my child component OfferList is not. From my expectation, the observer on my parent component should be enough. The parent component should already detect the change in the store and rerender. The child component in itself does not even use stores.
Overall, the problem at hand seems quite trivial so I guess I am just missing out on an important detail.
MobX only tracks data accessed for observer components if they are directly accessed by render, so if you want to react to each of the offers you need to access them somewhere. You sort of did when you tried merchantStore.offers.toString(), that's why it worked.
So first of all you need to make OfferList an observer.
But then you have FlatList which is native component and you can't make it an observer. What you can do is to access each offers item inside OfferList (just to subscribe for updates basically) like that data={offers.slice()} or even better with MobX helper method toJS data={toJS(offers)}
Depending on your use case you might also want to use <Observer> inside renderItem callback:
const renderItem = (offer: ListRenderItemInfo<any>) => {
return (
<Observer>{() => <Text>{offer.name}</Text>}</Observer>
)
}
I have a custom component like this
const MyCustomComponent = ({ value, style }) => {
// I can access value & style with value and style
return <View style={style}>
<Text>{value}</Text>
</View>
}
I can called it with
<MyCustomComponent value="123" style={{ color: "blue" }} />
My question is how to get arguments or alyelse to get all props passed to my component?
In native function, i can use arguments to get allProps as an Array and set it in a new variable like const allProps = arguments[0]
What about in arrow function?
What you have here is a functional component, it is built as such that it only receives one object - that is props. When you did this: const MyComponent({value, style}) you destructured the prop object, extracting only the two variables.
You should instead do this:
const MyCustomComponent = (props) => {
//you can access the values like this
console.log(props.style, props.value)
//or you can access them like this which is the same thing you did
//earlier
const {style, value} = props;
console.log(style, value)
return (
...
)
}
Keep in mind that you need to have React in scope, so be sure to import it at the top:
import React from 'react';
Functional components are very well explained in React documentation: https://reactjs.org/docs/components-and-props.html
Do you want to get 'value' and 'style' props using a single variable?
You can use:
const MyCustomComponent = (props) => {
// I can access value & style with value and style
return <View style={props.style}>
<Text>{props.value}</Text>
</View>
}
I have a TextInput component, which calls a custom function when its value changes through its onValueChange property.
Is it possible to simulate the change of the text of this TexInput with Jest, so that I test if that the custom function (which is passed as a prop) is called with correct parameters and produces correct results?
Yes its possible with jest and enzyme. Below is the definition of SearchBar component which receives a function as a prop. The function is called with the text typed.
const SearchBar = ({onSearch}) => {
return (
<View>
<TextInput
onChangeText={text => onSearch(text)}
/>
</View>
);
};
The testing can be carried as follows
const onSearch = jest.fn();
const wrapper = shallow(<SearchBar onSearch={onSearch} />);
wrapper.find('TextInput').simulate('changeText', 'test search text');
expect(onSearch).toHaveBeenCalledWith('test search text');
I'm experiencing a strange issue where the onPress function of a functional child component is triggered whenever the component is rendered.
Relevant parent code which renders the child component from a list of dates. I was also having issues with array.map if the component was a class component, but that's a separate issue.
return list_items.map(data =>
<ChildComponent
key={data.day}
props={data}
/>);
And the child component:
const ChildComponent = ({ props }) => {
let date = props.date;
const someFunction = () => {
console.log(date)
}
return (
<Text><Icon name="angle-right" onPress={someFunction()}/></Text>
)
And what I get from the console upon rendering the relevant scene:
2018-11-11
2018-11-12
2018-11-13
2018-11-14
2018-11-15
2018-11-16
Pressing the icon does nothing, yet it seems to run the function as if it was a lifecycle component. I've also moved the function into the parent component and passed it to the child as recommended per Guruparan Giritharan, and it still executes on render.
return list_items.map(data =>
<ChildComponent
key={data.day}
props={data}
someFunction={this.someFunction} // function now within parent component
/>);
And the updated child:
const ChildComponent = ({ props, someFunction }) => {
return (
<Text>
<Icon name="angle-right" onPress={someFunction(props.date)}/>
</Text>
)
}
Change to
<Text><Icon name="angle-right" onPress={someFunction}/></Text>
You are calling the function there itself instead of passing the reference. So the function is called inside each render instead of onPress.
From what I have read its best to try and structure react apps with as many components as "dumb" renderers. You have your containers which fetch the data and pass it down to the components as props.
That works nicely until you want to pass functions down the chain that require arguments other than events.
class MyClass extends Component {
_onItemPress (myId) {
// do something using myId
}
render () {
return <MyComponent myID={10} onPress={(myId) => this._onItemPress(myId)} />
}
}
If I simply pass that as my onPress handler to MyComponent it won't return myId when called. To get around this I end up doing something like this.
export default ({myId, onPress) => {
const pressProxy = () => {
onPress(myId)
}
return (
<TouchableHighlight onPress={pressProxy}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
Am I doing this completely incorrectly? I would like to be able to have a simple component that I can re-use for list items where its sole function is to take a title, onpress function and return a list item to be used in ListViews renderRow function. Many of the onPress functions will require variables to be used in the onPress however.
Is there a better way?
The proper syntax would be something like this:
render () {
let myId = 10;
return <MyComponent myID={myId} onPress={() => this._onItemPress(myId)} />
}
Also, if you plan to use this inside _onItemPress (for example to call other methods in MyClass), you need to bind the scope like this:
render () {
let myId = 10;
return <MyComponent
myID={myId}
onPress={() => this._onItemPress(myId).bind(this)} />
}
...or you can bind it already in the constructor if you prefer:
constructor(props) {
super(props);
this._onItemPress = this._onItemPress.bind(this);
}
You did it correctly.
MyComponent is as "dumb" as it should be: it does not care about the source of its props, it acts independently from higher level of logic of the app and it can be reused somewhere else in the app.
Some improvements you can work on:
MyComponent does not need myId itself. Exclude it from the component and let parental component deals with related logics to id
Provide a safe check for props onPress. If you want to reuse MyComponent somewhere, it is better to check for existence of onPress property before calling it, or provide a default value for onPress in case developer adds in unwanted props types.
Example of MyComponent
class MyComponent extends Component {
handlePress = (e) => {
if (typeof this.props.onPress === 'function') {
this.props.onPress()
}
}
render() {
return (
<TouchableHighlight onPress={this.handlePress}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
}
and to call MyComponent in MyClass:
class MyClass extends Component {
_onItemPress(myId) {
}
render () {
return <MyComponent onPress={() => this._onItemPress(10)} />
}
}