How to read props from a React Native touchable event currentTarget? - react-native

I have the following React Native code that runs the press() method when a user taps an image. I want to get the itemIndex prop from the event object. I set a break point in the press method and added some expressions to the Watch. From the Watch I determined that the target (event origination) from the event is the Image which is correct. The itemIndex prop is also available. The element being processed is the currentTarget, the Watch sees it's a "RCTView" and I was expecting a TouchableOpacity, so maybe underneath TouchableOpacity is a View? The currentTarget itemIndex prop is undefined, why? How can I get the props from the currentTarget?
I want to do it this way to avoid creating addition methods for each rendered item.
FYI,
ref={(c) => this._input = c} will not work because it's being run in a loop.
onPress={(e) => this.press(e, i)} creates a new function which I'm trying to avoid.
Watch
target._currentElement.props.itemIndex: 2
target._currentElement.type.displayName: "RCTImageView"
currentTarget._currentElement.props.itemIndex: undefined
currentTarget._currentElement.type.displayName: "RCTView"
press: function(event){
var currentTarget = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget);
var target = ReactNativeComponentTree.getInstanceFromNode(event.target);
var currentTargetIndex = currentTarget._currentElement.props.itemIndex;
var targetIndex = target._currentElement.props.itemIndex;
var url = this.state.data.items[currentTargetIndex].url;
Linking.openURL(url).catch(err => console.error('An error occurred', err));
},
render: function() {
return (
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.galleryView}>
{
this.state.data.items.map((data, i) =>
<TouchableOpacity itemIndex={i} key={i} activeOpacity={0.5} onPress={this.press} >
<Image itemIndex={i} key={i} source={{uri:data.previewImageUri}} style={styles.galleryImage} />
</TouchableOpacity>
)
}
</ScrollView>
);
}

I actually came across this same issue recently, I found two different ways you could approach this. The easier way of doing it is altering your onPress to pass an index to your press function, this is the 2nd way of doing it:
press: function(event, index){
var url = this.state.data.items[index].url;
Linking.openURL(url).catch(err => console.error('An error occurred', err));
},
render: function() {
return (
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={styles.galleryView}
>
{
this.state.data.items.map((data, i) =>
<Images data={data} key={i} index={i} press={this.press} />
)
}
</ScrollView>
);
}
const Images = (props) => {
const imageClicked = (e) => {
props.press(e, props.index);
}
return (
<TouchableOpacity activeOpacity={0.5} onPress={imageClicked} >
<Image source={{uri:props.data.previewImageUri}} style={styles.galleryImage} />
</TouchableOpacity>
)
}

You could make your event handler a curried function that accepts extra parameters.
//Curried function for onPress event handler
handleOnPress = (someId, someProp) => event => {
//USE someProp ABOVE TO ACCESS PASSED PROP, WHICH WOULD BE undefined IN THIS CASE
//Use event parameter above to access event object if you need
console.log(someProp)
this.setState({
touchedId: someId
})
}
Checkout the working snack below
https://snack.expo.io/#prashand/accessing-props-from-react-native-touch-event

Binding the needed information to a callback and assigning one to each child avoids recreating the callback on every render of children.
class Hello extends React.Component{
state = { names: this.props.names.map((name, i) => {
return Object.assign({
onClick: this._onClick.bind(this, i, this.props),
}, name)
}),
};
_onClick(ind, _props, e) {
alert('props:' + JSON.stringify(_props));
}
render() {
const { names } = this.state;
return (
<div>
{ names.map((name, i) => (
<div key={i}>Name: <input value={ name.first } onClick={ name.onClick } /></div>
))}
</div>
)}
}
ReactDOM.render(
<Hello names={[{first:'aaa'},{first:'bbb'},{first:'ccc'}]}/>,
document.getElementById('container')
);
JS Fiddle

Related

Iterate over values of Map to render all icon components but don't work however render one icon works

I am developing react-native project.
I have a function which set icon metadata into a Map :
export function getIconsMetadata() {
// a map of icons' metadata
const iconsMetadata = new Map();
...
// code to set icon metadata to the map
iconsMetadata.set("foo", "Foo");
iconsMetadata.set("bar", "Bar");
...
return iconsMetadata;
}
There is another function which returns the actual icon component based on the icon type (i.e. the value of iconsMetadata holds the icon type):
export function getMyIcon(iconType) {
switch (iconType) {
case 'Foo':
return <Foo />;
case 'Bar':
return <Bar />;
...
}
In my screen, I have a function to show icon component by iterating over the values of the above icons' metadata Map, and try to render each icon component:
export const MyScreen() => {
const showIcons = () => {
[...getIconsMetadata().values()].map((iconType, index) => {
const iconComponent = getMyIcon(iconType);
return <View key={index}>
{iconComponent}
</View>;
});
};
return (
<View style={styles.container}>
{/*I call the showIcons function here to render icons*/}
{showIcons()}
</View>
)
}
Problem is the icons are not shown on screen.
But if I directly return one icon component in my screen:
export const MyScreen = () => {
...
const showOneIcon = () => {
return <View>
<Foo />
</View>;
});
}
return (
<View style={styles.container}>
{/*I show one icon*/}
{showOneIcon()}
</View>
)
}
The <Foo /> icon component is rendered successfully on the screen.
So, why iterating the map to show all icons don't work?
The problem is that you’re not returning anything from showIcons. Either you remove { } from there
const showIcons = () =>
[...getIconsMetadata().values()].map((iconType, index) => {
const iconComponent = getMyIcon(iconType);
return <View key={index}>{iconComponent}</View>;
});
or add return before [...getIconsMetadata().values()].map
const showIcons = () => {
return [...getIconsMetadata().values()].map((iconType, index) => {
const iconComponent = getMyIcon(iconType);
return <View key={index}>{iconComponent}</View>;
});
};

loading on navigating between screens

I'm new in RN. When I want to navigate between screens I create this function:
displayScreen2 = () => {
this.props.navigation.navigate("screen2")
}
and I call it in onPress={this.displayScreen2}
with TouchableOpacity or any Touchable when the user clicks he has to wait 1 second or 2 before displaying the screen. So what I want is to change the Touchable icon to an loader.
It's simple if I use a conditional rendering but I don't know how to do it now, when I have to change my state? Any suggestions?
this is my approach:
<TouchableOpacity
style={Styles.topButton}
onPress= {() => {
this.setState({loading: 'load'},
() => {
displayScoreListView()
// this.setState({loading: 'icone'})
}
)
}}
>
<Text style={Styles.scoreListButtonTextRed}>{this.state.loading}</Text>
that not work, tha state change but visualy not because if I return to the first screen I have 'load' in the text component
You could create a custom component wrapping whatever Touchable you prefer, I've used this technique in my production apps before. The button has it's own state which allows you to automatically display a loading indicator when necessary.
export class ButtonWorker extends Component {
state = {
working: false
}
onButtonPress = () => {
this.setState(
{ working: true },
() => {
this.props.onPress(this.onWorkFinished);
}
);
}
onWorkFinished = () => {
this.setState({ working: false });
}
render() {
return (
<TouchableOpacity>
{this.state.working ? (
<ActivityIndicator />
) : (
this.props.children
)}
</TouchableOpacity>
);
}
}
And then use it like a normal button with additional logic!
export class NavigationScreen extends Component {
navigate = (done) => {
// ... Asynchronous logic goes here
done();
this.props.navigation.navigate("Screen2");
}
render() {
return (
<Fragment>
{/* ... */}
<ButtonWorker onPress={this.navigate} />
</Frament>
);
}
}

How to change checkbox status in React Native

Here I want to change checkbox status. After clicking to checkbox no change does not appear. But after CTR+S in VS Code the data is updated.
const [data,setData] = React.useState();
React.useEffect(() => {
DB.select("tasks","*").then(res=>{
return setData(res);
});
}, [100])
const complete = (index,id)=>{
data.forEach((x,k)=>{
if(k == index){
x.status = !x.status;
alert(x.name);
DB.update("tasks",["status"],[x.status],'id=?',[x.id]).then(r=>{
console.log(r);
});
setData(data);
}
});
}
<FlatList
style={css.list}
data={data}
extraData={data}
renderItem={({ item,index }) =>
<View style={css.item}>
<View style={css.textItem}>
<Text>{item.name}</Text>
<Checkbox
status={item.status ? 'checked': 'unchecked'}
onPress={e=>complete(index,id) }
/>
</View>
<View style={css.chip}><Text style={{alignItems: 'center',color: "#009386"}}>Category #1</Text></View>
</View>}
keyExtractor={data => data.id}
/>
You're setting the same data which was on the state. So it won't update.
In addition
It is very bad practice to direct;y mutate the state variable what you're doing in complete function. Use map instead of forEach.
Update your complete function as follows:
const complete = (index, id) => {
const updatedData = data.map((x, k) => {
x.status = x.status ? 0 : 1;
DB.update("tasks", ["status"], [x.status], 'id=?', [x.id]).then(r => {
console.log(r);
});
return x;
});
setData(updatedData);
}
As you've told that status contains number 0/1, I'm updating status on check with 0 and 1 unlike you're saving boolean.
I code in TS, so I'm always aware of types :)

React Native: How to change inputfield to selected item

So what I'm trying to do is fetching data from an API (works well), that has this autocomplete function.
Link to example: https://autocomplete.aws.dk/
Link to the guide: https://autocomplete.aws.dk/guide2.html
What is hard for me, is that the guide is HTML, and this is suppose to work in React Native.
So far I made an input field, that can detect when writing minimum two letters will show a list of addresses.
What I want is when the address is clicked, it takes that value and places it in the input field.
Heres my code:
The API fetch:
import React from "react";
import url from "./url";
export default class DawaFetch extends React.Component {
static defaultProps = {
options: {},
minCharacters: 2,
};
state = {
value: "",
suggestions: [],
};
handleChange = ({ target: { value } }) => {
this.setState({ value });
if (this.props.minCharacters <= value.length) this._fetch(value);
};
_fetch = (value) => {
fetch(
url("https://dawa.aws.dk/adresser/autocomplete", {
q: value,
["per_side"]: 100,
...this.props.options,
}),
{
method: "GET",
headers: {
"Accept-Encoding": "gzip, deflate",
},
}
)
.then((response) => response.json())
.then((json) => this.setState({ suggestions: json }))
.catch((err) => console.error("parsing failed", err));
};
render = () =>
this.props.children({ ...this.state, handleChange: this.handleChange });
}
And here is my view:
<DawaFetch>
{({ value, suggestions, handleChange }) => {
console.log(suggestions);
return (
<View>
<CustomInputs
type="text"
value={value}
onChange={handleChange}
/>
{suggestions.map((suggestion) => (
<TouchableOpacity>
<NormalText key={suggestion.adresse.id}>{suggestion.tekst}</NormalText>
</TouchableOpacity>
))}
</View>
);
}}
</DawaFetch>
Tried different solutions by making it a FlatList with renderItem, and making an onPress function, but I could never make it work.
Hopefully someone can guide me in the right direction, I might been overthinking this.
React-Native TextInput onChange value is not available inside the target as it's available in HTML, Remove target from handleChange function like below, also it's not onChange it's onChangeText in TextInput.
handleChange = (value) => {
this.setState({ value });
if (this.props.minCharacters <= value.length) this._fetch(value);
};
You can achieve your desired functionality in a very simple manner.
Add this to your DawaFetch class.
OnItemSelection=(address)=>{
this.setState({value: address})
}
Add this to your render Function
render = () =>
this.props.children({ ...this.state, handleChange: this.handleChange, OnItemSelection: this.OnItemSelection });
}
Then make these changes in your DawaFetch component
<DawaFetch>
{({ value, suggestions, handleChange, OnItemSelection }) => {
console.log(suggestions);
return (
<View>
<CustomInputs
type="text"
value={value}
onChangeText={handleChange}
/>
{suggestions.map((suggestion) => (
<TouchableOpacity onPress={()=> OnItemSelection(suggestion.adresse)}>
<NormalText key={suggestion.adresse.id}>{suggestion.tekst}</NormalText>
</TouchableOpacity>
))}
</View>
);
}}
</DawaFetch>
Edit:Here is Snack having solution
https://snack.expo.io/#waheed25/bad-raisins

Function inside renderItem in flatlist

I have a flatlist and it's working fine, I'm trying to put a function inside the code that is rendering my item but it gives me an error, and i need to put the function there so i can read the item's properties, here is my code:
_renderItem ({ item, index}) {
_shareText() {
Share.share({
message: item.name,
})
}
return (
<Button title="Share" onPress={this._shareText}/>
);
}
try this, i can see that _shareText is not defined like a function in the component
_shareText(item) {
Share.share({
message: item.name,
})
}
_renderItem({ item, index }) {
return (
<Button title="Share" onPress={() => { this._shareText(item); }} />
);
}