Where to implement initialization code in react-native functional component - javascript

I am following the functional pattern as given by the expo project creation wizard, and I have a component like this:
Search.js
export default function Search() {
const [searchResults, setSearchResults] = React.useState(buildContentViews(contents));
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.contentContainer}>
<View style={styles.statusLine}>
<Text style={styles.statusLineText}>{(pageInfo.numResults || 0) + ' Treffer'}</Text>
</View>
{searchResults}
</ScrollView>
</View>
);
}
Now I have some non-react-native implementation for backend REST-services, which shall regularly update the search results. Therefore I would need to do something like:
export default function Search() {
const [searchResults, setSearchResults] = React.useState(buildContentViews(contents));
client.events.on('searchResults', (results) => setSearchResults(results));
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.contentContainer}>
<View style={styles.statusLine}>
<Text style={styles.statusLineText}>{(pageInfo.numResults || 0) + ' Treffer'}</Text>
</View>
{searchResults}
</ScrollView>
</View>
);
}
However I quickly get the error that too many event listeners are established, which is probably because the above code gets run every time the component is (re-)rendered, or in other words, whenver the component is updated.
So how would I correctly register the event listener and/or deregister the event listener in this scenario?

useEffect hook is your friend here!
This is how I handle registering/deregistering to a react-navigation event moving between the screens (I don't know how your client code works, this is just an example)
useEffect(() => {
const onFocus = () => {
// here I do something when the screen gets focused
}
// this is how you handle the registration to the event
const focusListener = navigation.addListener('didFocus', onFocus)
// and this is how to handle deregistration!
return () => focusListener.remove()
}, []) // empty array of dependencies
In the body of the useEffect hook, you define your actions;
the return function is used to clean-up effects, the perfect place to remove an event listener;
the empty array of dependencies assures you that this code will be executed just one time (after the first render) and no more! No more reallocation! The perfect way to define an event listener!

Related

Handling touch in React Native with time

I am working on an onPress event in React Native. I have added two different actions onPress, onLongPress. I have two different functions associated with each of them. Now i have also added delayLongPress={250} and it's working with onLongPress. But when i try to call the onLongPress, it calls the onPress too. I don't want that to happen. I want to call the onpress when it's pressed just once and onLongPress when it's pressed for 250ms at least. How can i seperate those function calls.
Here's what i have right now:
const onLongPress = () =>{
console.log('Pressed long')
}
const onChange = () =>{
console.log('Pressed')
}
return(
<Container
onPress={onChange}
onLongPress={onLongPress}
delayLongPress={250}
>
</Container>
)
Try to wrap it with the TouchableHighlight.
export default function App() {
const onLongPress = () => {
console.log('Pressed long');
};
const onChange = () => {
console.log('Pressed');
};
return (
<TouchableHighlight
onPress={onChange}
onLongPress={onLongPress}
delayLongPress={250}
>
<Text>Press</Text> // Your child components goes here
</TouchableHighlight>
);
}
See the snack here

How to avoid re rendering in react native?

I'm trying to avoid re-rendering unnecessary components
For example:
const[ValueState,SetValueState]=useState(5); //hook
<View>
<Text>{ValueState}</Text>
<View>
{
[...Array(3)].map((index,el)=>{
return (<View><Text>Hello there</Text></View>)
})
}
</View>
</View>
here every time I change the value of ValueState the entire map() segment also gets re-rendered
how do I avoid this and make the map() segment only render 1 time?
It depends on what the function you don't want to re-render depends on. In this case, your array and map function do not depend directly to ValueState.
One way to achieve 1 re-render is using React.memo
Example to render map function only once
import React, { useState } from "react";
import { View, Text, TouchableOpacity } from "react-native";
const ArrayMapSection = React.memo(()=> {
console.log("ArrayMapSection rendered")
return [...Array(3)].map((index,el)=>{
return (<View><Text>Hello there</Text></View>)
});
})
const App = () => {
const [ValueState,SetValueState]=useState(5); //hook
return(
<View>
<Text>{ValueState}</Text>
<TouchableOpacity onPress={()=>SetValueState(Math.random())}>Press to state update</TouchableOpacity>
<View>
<ArrayMapSection />
</View>
</View>
)
};
export default App;
If you run this program, you would see ArrayMapSection renderedonly once in the console. Now try to change the ValueState by pressing on Press to state update. The ArrayMapSection won't re-render because React.memo only re-renders if props changes
More info: https://reactjs.org/docs/react-api.html#reactmemo
Create a custom react component that takes the array as a prop and maps it to other components.
That way the component is only rerenderd if the array prop changes.
Code example:
const[ValueState,SetValueState]=useState(5);
<View>
<Text>{ValueState}</Text>
<CustomList array={[1,2,3]} />
</View>
export const CustomList = (array) => {
return (
<>
{
array.map((index,el)=>{
return (<View><Text>Hello there</Text></View>)
})
}
<\>
)
}

setState function comes up as undefined when passed to helper

I am trying to take a picture, and when that picture returns its prediction in the backend update the state. However, I cannot get the function to pass in, it only comes up as undefined. I know redux would probably be the best solution for this, but this is the only piece of state in the whole app where I have an issue, so I would rather avoid it if possible.
Here is my file:
const CameraRender = (props) => {
const [predictionLoaded, isPredictionLoaded] = useState(false);
const [showModal, changeModalVisability] = useState(false);
const [proportions, setProportions] = useState([]);
const { setCameraInactive } = props;
return(
<View style={styles.container}>
<Camera
style={styles.container}
ref={ref => {
this.camera = ref;
}}
>
<View style={styles.rectangle} />
<View style={styles.pictureButton}>
<Ionicons
name='ios-camera'
size={60}
color={'#cdd2c9'}
onPress={() => pictureHandler(this.camera, isPredictionLoaded(true),
changeModalVisability(true), ((p1, p2) =>
setProportions([p1, p2])))}
/>
</View>
</Camera>
{showModal &&
<PostPicModal
predictionLoaded
proportions
setCameraInactive={() => setCameraInactive()}
changeModalVisability={() => changeModalVisability(false)}
/>
}
</View>
);
}
const pictureHandler = async (camera, addPic, isPredictionLoaded,
changeModalVisability, setProportions) => {
const photo = await this.camera.takePictureAsync({ onPictureSaved: this.camera.pausePreview()});
changeModalVisability();
const prediction = await requestPrediction(photo.uri);
setProportions(prediction[0][0], prediction[0][1]); //THIS ISNT WORKING
isPredictionLoaded();
}
I know this is easy enough when I am passing it to another functional component, but I really just wanted to have a helper function that could take in the state. Should I just move picture handler right into the onPress? Thanks for the help.
Issue is with argument passing.
You are passing 4 arguments, however in your function you have expected 5 arguments.
I believe addPic is missing argument when you are calling a function.
I can see issue in passing arguments as well, it should be just isPredictionLoaded and inside the function you can call it with true or false like isPredictionLoaded(true)

How to pass a paramater multiple times through React-Navigation in React-Native?

function A() {
const [levels, setLevels] = React.useState(null);
// upon the component mount, we call the database.
React.useEffect(() => {
database()
.ref()
.once("value")
.then((snapshot) => {
//get Levels from db
const levels = snapshot.map((child) => child.key);
setLevels(levels);
});
}, []);
return (
<View>
{levels.map((element) => {
return <B level={element} />;
})}
</View>
);
}
function B(props){
const navigation = useNavigation();
return (
<View>
<TestButton
title={props.level}
onPress={() => navigation.navigate('C')}
/>
</View>
);
}
I currently am already passing the value 'level' to the TestFunc parameter, but now I wish to pass the same parameter to the screen I navigate to when I press the TestButton component.
https://reactnavigation.org/docs/params/
This documentation shows me how I would do such with a newly initialized parameter, but it doesn't seem to work with parameters that have been passed from previous screens.
Any suggestions?
EDIT:
I have tried to make it a little bit clearer. I want to go from function A to B to C, all different components/screens in my app. I want to take the 'level' that I obtain from function A and pass it from B to C. Currently I am correctly retrieving the 'level' value in function B, however I can not seem to pass it to function C.
function TestFunc (props){
const navigation = useNavigation();
return (
<View>
<TestButton
title={props.level}
onPress={() => navigation.navigate('Category',{...props})}
//{...props} send all props that's exist in props argument
/>
</View>
);
}
You can get the all props on Category component also
const { level } = this.props.navigation.state.params;

React Native: can't a method be used from 'routeMapper'?

I’m using react native drawer of https://github.com/root-two/react-native-drawer
I am trying to call the method openDrawer() by passing in variable into NavigationBarRouteMapper. I tried logging inside NavigationBarRouteMapper, and it logs the variable passed in correctly. But when it used inside the NavigationBarRouteMapper, by clicking the Left Navigation button of ‘Open Drawer’, it does not do anything:
class drawerPractice extends Component {
...
openDrawer(){
this._drawer.open()
}
render() {
return (
<Drawer
content={<DrawerPanel/>}
openDrawerOffset={100}
ref={(ref) => this._drawer = ref}
type='static'
tweenHandler={Drawer.tweenPresets.parallax}
>
<Navigator
configureScene={this.configureScene}
initialRoute={{name: 'Start', component: Start}}
renderScene={this.renderScene}
style={styles.container}
navigationBar={
<Navigator.NavigationBar
style={styles.navBar}
routeMapper={NavigationBarRouteMapper(this.openDrawer)}
/>
}
/>
</Drawer>
);
}
}
var NavigationBarRouteMapper = openDrawer => ({
LeftButton(route, navigator, index, navState){
return(
<TouchableHighlight onPress={()=>{openDrawer}}>
<Text>Open Menu</Text>
</TouchableHighlight>
)
}
},...
Why may be the issue?
On your constructor() method add this: this.openDrawer = this.openDrawer.bind(this);
You're likely using this on the wrong scope.
There is a common confusion when working with React components and the new ES6 extends syntax. If you use React.createClass, it will bind this to all of your functions, but when using the ES6 approach of extends React.Component you have to bind your functions manually.
You can do it either inline using
<TouchableHighlight onPress={()=>{this.openDrawer.bind(this)}}>
Alternatively, you can add to your constructor, after super():
this.openDrawer = this.openDrawer.bind(this);
Personally I like this approach as I find this code a bit easier to read.
For more information about the ES6 way, check this link.

Categories

Resources