Methods inside of functional components - react-native

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.

Related

Jest: Getting warning when try to mock a component

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?

Child component not rerendered on prop change

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>
)
}

react native How to get child component const value to parent screen component

I have one text input in my child component and I want to get the text input data to my parent screen. I want to pass the data to one component to another component.
You have 2 components parent and child suppose , then it would be like
In render of Parent , basically you are passing a callback function to child
textChange = (value) => {
this.setState({newText:value});
}
render(){
return(
<Child onTextInput={this.textChange} />
)
}
and in child's render:
render(){
return(
<TextInput onChangeText={e => this.props.onTextInput(e)}
)
}
Hope it helps.

undefined is not a function (evaluating 'this.props.navigation.state.params

I have a parent and child, I am using navigator to route between them, I have some state in the child component and I want to pass the state value in that component to the parent without routing the parent.
so far I have the code as follow:
on my parent:
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate('Riddles', {
onChangeCount: this.onChangeCount.bind(this)
})}
>
as you can see on my parent (which is the home screen) I have butten that onPress navigate to Riddles screen (which is my child component)
on the child component I setup as follow:
nextQuestion() {
this.setState({
userAnswer: '',
count: this.state.count + 1,
diamonds: this.state.diamonds + 5
})
const { navigate } = this.props.navigation
this.props.navigation.state.params.onChangeCount(1)
this.refs.modalCorrect.close()
this.saveData()
}
and render it (on child - riddles screen ):
return (
<TouchableOpacity
style={styles.modalButtonAnswer}
onPress={() => this.nextQuestion()}
>
as much as I understod, the line " this.props.navigation.state.params.onChangeCount(1)" send the value "1" to my parent screen and in the parend screen I need to bind this (https://github.com/react-navigation/react-navigation/issues/1416)
, but it doesn't work for me, please help....

React Native passing functions with arguments as props

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)} />
}
}