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 :)
Related
My idea is that when someone put a text in my TextInput and clicks submit then botToc() will save the data from that API search in result and show the result in a FlatList.
But I have a problem saving data with hooks. The thing is that my setResult is not working properly because it does not save well the data from the API query. botToc() is supposed to fetch data from an API and its doing that fine but then I loop through the fetched data and I save what I want in result using setResult.
The thing is that when I click the button that uses botToc2() ( after clicking the button that uses botToc() ) my console.log(result) shows only the data of the last element and if I use again botToc() and I click one more time botToc2() I get that last element duplicated in the result array.
How can I fix this?
export default function TestScreen () {
const [querypar, setQuerypar] = useState('');
const [apipar, setApipar] = useState('https://API/search?q=');
const [result, setResult] = useState([]);
const ItemView = ({item}) => {
return (
<View>
<Text>
{item[0]+ ' ' + item[1]}
</Text>
</View>
);
};
function botToc() {
let query = apipar+querypar; //'https://API/search?q='+'TextInputText'
let cargador = [];
fetch(query) //'https://API/search?q=TextInputText'
.then( res => res.json() )
.then( res => {
// (res.results) = [{price:x,title:x},{price:x,title:x},{price:x,title:x}] structure of (res.results)
(res.results).forEach( (element) => {
cargador.push(element.title);
cargador.push(element.price); //cargador=[x,x]
setResult([...result, ...cargador]); //result=[[x,x],[x,x]...]
cargador.length = 0; //cargador=[]
});
})
};
function botToc2() {
console.log(result); //my console should return [[x,x],[x,x],[x,x],[x,x],[x,x],[x,x],...]
};
return (
<View View style={styles.container}>
<TextInput placeholder="write here" onChangeText={(val) => setQuerypar(val)} />
<View>
<Button onPress={botToc} title="Submit"/>
<Button onPress={botToc2} title="Submit"/>
<FlatList
data={result}
renderItem={ItemView}
keyExtractor={(item, index) => index.toString()}
/>
</View>
</View>
);
};
Calling setResult() multiple times in the forEach function should be avoided. In that case your botToc() function should look like this:
function botToc() {
let query = apipar+querypar; //'https://API/search?q='+'TextInputText'
let cargador = [];
fetch(query) //'https://API/search?q=TextInputText'
.then( res => res.json() )
.then( res => {
// (res.results) = [{price:x,title:x},{price:x,title:x},{price:x,title:x}] structure of (res.results)
(res.results).forEach( (element) => {
cargador.push([element.title, element.price]); //cargador=[x,x]
});
})
setResult(cargador);
};
This should do the job for you.
Sometghing really basic but I didn't understant.
Once I get the contacts how can I use them to populate the Flatlist?
I always get Can't find variable: contacts
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
const contact = data[0];
console.log(contact);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={contact}
keyExtractor={contact.id}
renderItem={({ item }) => (
<ContactItem
firstName={item.firstName}
/>
</View>
);
};
export default ContactsScreen;
I think it's really simple, I just don't understand
You need to keep your contacts in the component's state. So every time you change your state, your component will render itself and you will see the updated data.
Change your code with the following. Don't forget to import useState.
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
const [myContacts, setMyContacts] = useState([]);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
setMyContacts(data);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={myContacts}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Text>{item.firstName}</Text>
)}
/>
</View>
);
};
export default ContactsScreen;
Answer from my comment:
I think that might be because of the scope of the variable , it could be that RN doenst know it exists because it only lives inside the function. I guess you could set up a State and then assign the values from contact to the state and in ur flatlist call data ={ this.state.contact}.
or by using hooks like you do :
if (data.length > 0) {
setContact(data);
}
and call it in flatlist:
data={myContact} // if named so in state declaration
i have a like icon and value of status 0 or 1.want to show thumbs up icon if status is 1 and thumbs down if status is 0.and toggle the same when clicked
<View style={{marginTop:10}}><Icon name={itemData.wliststatus===0?"thumbs-up":"thumbs-down"} size={16} type="font-awesome" onPress={() => this.navigateToApp(itemData)}/></View>
Fetched from API
itemdata:{
"userid": "1",
"views": "258",
"wliststatus": 0,
}
<View style={{marginTop:10}}><Icon name={itemData.wliststatus===0?"thumbs-up":"thumbs-down"} size={16} type="font-awesome" onPress={() => this.navigateToApp(itemData)}/></View>
First create state variable that holds whliststatus value and create function that toggle value of state variable.
state = { whliststatus = false }
const handlePress = itemData => this.setState({ whlistatus: !this.state.whlistatus }, () => { this.navigateToApp(itemData)} })
<View style={{marginTop:10}}><Icon name={this.state.whliststatus ? "thumbs-up" : "thumbs-down"} size={16} type="font-awesome" onPress={this.handlePress(itemData)}/></View>
Use state to change items dynamically.
First, in state variable, set your like status value as false.
Remember: the state variable will reside globally in your class
state = {
liked: false
};
Then in componentDidMount, after successfully fetching data, update your status accordingly in state:
componentDidMount() {
fetch(...)
.then(res => res.json())
.then(res => {
...
itemdata:{
"userid": "1",
"views": "258",
"wliststatus": 0,
}
// here update "liked" status in state
this.setState({ liked: itemdata.wliststatus == 0 ? true : false });
...
})
.catch(error => console.log(error));
}
Now, come directly to the render method, get or set the state and data will be handled automatically:
render() {
const { liked } = this.state;
return (
<View style={{marginTop:10}}>
<Icon name={ liked ? "thumbs-up" : "thumbs-down" }
size={16} type="font-awesome"
onPress={() => this.navigateToApp()}/>
</View>
);
}
Then as it seems you might be updating the like in this.navigateToApp function. There in this method you can update your state as:
navigateToApp() {
const { liked } = this.state;
...
// here just switch your liked status
this.setState({ liked: !liked });
...
}
I'm trying to display the time zone in another screen when an item is pressed in previous flatlist. My data is coming from autocomplete when I'm selecting it is displayed in flatlist.
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={autotime.length === 1 && comp(query, autotime[0].name) ? [] : autotime}
defaultValue={this.state.timeZone}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter Location"
renderItem={({ name, release_date }) => (
<TouchableOpacity onPress={() => this.setState({ query: name,timezoneArray:autotime[0].timezones })}>
<Text style={styles.itemText}>
{name}
</Text>
</TouchableOpacity>
)}
/>
<View style={styles.descriptionContainer}>
{autotime.length > 0 ? (
<FlatList
style={{flex:1}}
data={this.state.timezoneArray}
renderItem={({ item }) => <TimeZoneItem text={item} />}
/>
) : (
<Text style={styles.infoText}>Enter Location</Text>
)}
I want that when I press the items of flatlist it is displayed on another page.
Picture below shows what I have made:
My Data base helper class is:
export const SaveItem = (key, value) => {
AsyncStorage.setItem(key, value);
};
export const ReadItem = async (key) => {
try {
var result = await AsyncStorage.getItem(key);
return result;
} catch (e) {
return e;
}
};
export function MultiRead(key, onResponse, onFailure) {
try {
AsyncStorage.multiGet(key).then(
(values) => {
let responseMap = new Map();
values.map((result, i, data) => {
let key = data[i][0];
let value = data[i][1];
responseMap.set(key, value);
});
onResponse(responseMap)
});
} catch (error) {
onFailure(error);
}
};
export async function DeleteItem(key) {
try {
await AsyncStorage.removeItem(key);
return true;
}
catch (exception) {
return false;
}
}
and here i have added my code to save
handleTimezone = (text) => {
this.setState({ TimeZoneItem: text })
}
newData.TimeZoneItem = this.state.TimeZoneItem
this.setState({
TimeZoneItem: '',
})
ReadItem('timeData').then((result) => {
let temp = []
if (result != null) {
temp = JSON.parse(result)
} else {
temp = []
}
temp.push(newData)
SaveItem('timeData', JSON.stringify(temp))
console.log(`New Data: ${JSON.stringify(temp)}`)
}).catch((e) => {
})
}
<FlatList
style={{flex:1}}
data={this.state.timezoneArray}
renderItem={({ item }) => (
<TouchableOpacity>
<TimeZoneItem text={item} onPress={() => this.props.onPress()}
value={this.state.TimeZoneItem}
/>
</TouchableOpacity>)}
You'll need an array to saved all the saved items.
for example
state = {
saved: []
};
On press the time zone item, get the values from state, add the new item to the array and save the array to async storage using JSON.stringify()
onSave = item => {
const { saved } = this.state;
const newItems = [...saved, item];
this.setState({
saved: newItems
});
const items = JSON.stringify(newItems);
SaveItem("saved", items)
.then(res => {
console.warn("saved", res);
})
.catch(e => console.warn(e));
};
Then in your other screen get the items in using your ReadItem function like.
state = {
saved: []
};
componentDidMount = () => {
ReadItem("saved")
.then(res => {
if (res) {
const saved = JSON.parse(res);
this.setState({
saved: saved
});
}
})
.catch(e => console.warn(e));
};
Working Demo
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