React native picker, get selected option text - react-native

I am using my react native picker with options like so:
<Picker
selectedValue={student}
label="Student"
style={styles.picker}
onChange={this.onStudentChange}
options={
categories
.find(category => {
return category.id == year;
})
.options.find(category => {
return category.id == group;
})
.options
}
/>
I then dispatch and action in my click handler, where e is the id of the student:
onStudentChange(e) {
if (e !== "") {
this.props.setStudent(e);
}
}
How can I get the selected option text as well as the value?

You can find the selected option by id from the list of options. After that you can get a text from the selected option. In the example below I moved the code that gets the options into a separate function which I also use in onStudentChange:
getOptions(year, group) {
return categories
.find(category => {
return category.id == year;
})
.options.find(category => {
return category.id == group;
})
.options
}
onStudentChange(e) {
if (e !== "") {
// Find option by id and get text.
const text = this.getOptions().find(entry => entry.id === e).text;
this.props.setStudent(e);
}
}
render() {
...
<Picker
selectedValue={student}
label="Student"
style={styles.picker}
onChange={this.onStudentChange}
options={this.getOptions(year, group)}
/>
}

Related

Why does my context consumer not automatically update?

I am working on a react native app and I have an array of components that I need to be updated from different sections of my app. I am using context to be able to provide different consumers with this array, as well as the ability to the update the array.
As you can see in the code below I am passing my array, as well as a function to update the list, as the value for my provider. I then am using "header-right" in the navigation to call the function that updates the array in the provider state. I have confirmed that the array is updating and will only re-render the consumers when a change state in another component causes a re-render.
What I need is the change state of the provider to re-render all consumer components when it is changed, not when another components state change has occurred.
const DbContext = React.createContext("DEFAULT");
export default class DbProvider extends Component {
constructor(props){
super(props);
this.state = {
choreList: [],
refresh:null,
}
}
componentDidMount(){
sqlQueries.getChores().then(row => this.setChoresList(row));
}
updateChoreList = (choresAdded, offset) => {
console.log("K");
if(offset == 1){ // if adding chores
var tempList = this.state.choreList;
tempList.push(choresAdded);
this.setState({
choreList: tempList,
})
}else if(offset == -1){ // if removing a chore
for(var i = 0; i < this.state.choreList.length; i++){
for(var j = 0; j < choresAdded.length; j++){
if(this.state.choreList[i].state.name == choresAdded[i].state.name){ // if chore being removed is i in state choreList
var tempList = this.state.choreList; // remove and update state choreList
tempList.splice(i, 1);
this.setState({
choreList: tempList,
});
break;
}
}
}
}
}
setChoresList(row){
var tempList = [];
for(var i = 0; i < row._array.length; i++){
tempList.push(<Chore choreName={row._array[i].name} dayId={row._array[i].id} nav={this.props} inDB={true} key={row._array[i].name}/>);
}
this.setState({
choreList: tempList,
})
}
render () {
return (
<DbContext.Provider value={{choreList: this.state.choreList, update: this.updateChoreList}}>
{this.props.children}
</DbContext.Provider>
)
}
}
export { DbContext, DbProvider }
I am then in another component using this list to display something
render(){
return (
<DbProvider>
<DbContext.Consumer>
{globalChores => globalChores.choreList == 0
? <NoChores nav={this.props.nav} dayState={this} />
: (
<View style={styles.ChoreDayContainer}>
<View style={styles.ChoreListContainer}>
{globalChores.choreList}
</View>
<AddChoresBtn nav={this.props.nav} dayState={this}/>
</View>)}
</DbContext.Consumer>
</DbProvider>
);
}
and finally I am updating the provided array like so
componentDidMount(){
this.props.navigation.setOptions({
headerRight: () => (
<DbProvider>
<DbContext.Consumer>{(db) =>(
<Button
onPress={ () => this.dataToDB(db)}
title="Save"
color="#fff"
/> )}
</DbContext.Consumer></DbProvider>
),
})
}
dataToDB(db){
db.update(this.state.choreShowingList, 1);
}
}

How to use the $filter variable on graphql query under the Connect component?

I have a simple query auto-generated from aws AppSync, and I'm trying to use the Connect Component, with a FlatList and use a TextInput to filter and auto-update the list. But I confess I didn't found out a way to do that... any hints?
Tried to find more information about this without success...
Auto-Generated query:
export const listFood = `query ListFood(
$filter: ModelFoodFilterInput
$limit: Int
$nextToken: String
) {
listFood(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
name
description
...
My current code, which I don't quite know where to place my filter value:
<Connect query={graphqlOperation(queries.listFood)}>
{
( { data: { listFood }, loading, error } ) => {
if(error) return (<Text>Error</Text>);
if(loading || !listFood) return (<ActivityIndicator />);
return (
<FlatList
data={listFood.items}
renderItem={({item}) => {
return (
<View style={styles.hcontainer}>
<Image source={{uri:this.state.logoURL}}
style={styles.iconImage}
/>
<View style={styles.vcontainer}>
<Text style={styles.textH3}>{item.name}</Text>
<Text style={styles.textP}>{item.description}</Text>
</View>
</View>
);
}}
keyExtractor={(item, index) => item.id}
/>
);
}
}
</Connect>
What I aim is mainly to filter by item.name, refreshing the list while typing from a TextInput, probably going somewhere on the $filter variable...
Ok, I think I've figured out the usage with the AWS AppSync Out-of-the-box queries...
query MyFoodList{
listFood(
filter: {
name: {
contains:"a"
}
}
) {
items {
id
name
}
}
}
And it is finally working properly with this disposition on my react-native code:
<Connect query={ this.state.filter!=="" ?
graphqlOperation(queries.listFood, {
filter: {
name: {
contains: this.state.filter
}
}
})
:
graphqlOperation(queries.listFood)
}>
I still didn't manage to make the sort key work yet... will try a little more and open another topic for it if I didn't get anything...
This is filter in use in React / Javascript:
const [findPage, setFindPage] = useState('') // setup
async function findpoints() {
// find user & page if exists read record
try {
const todoData = await API.graphql(graphqlOperation(listActivitys, {filter : {owner: {eq: props.user}, page: {eq: action}}}))
const pageFound = todoData.data.listActivitys.items // get the data
console.log('pageFound 1', pageFound)
setFindPage(pageFound) // set to State
} catch (err) {
console.log(err)
}
}
The async / wait approach means the code will try to operate, and move on to other areas of your code putting data into findPage through setFindPage when and if it finds data

issue refer to scope of variable

how to get variable from outer layer method
trying to use a variable in outer layer in my React-Native App
updateCheckBox() {
Constants.TABS.map((item) => {//Constants.TABS is an array
AsyncStorage.getItem(item)//using item as key to fetch from AsyncStorage
.then((res) => {
if(res == 1) {
//debugged here, item was undeined. but i need setState here with item as key. How should i get item here.
this.setState({item: true}) // I need to get the item here, but it show undefined
} else {
this.setState({item:false}) // I need to get the item here, but it show undefined
}
})
})
}
// I need to get the item here, but it show undefined
You need to wrap the item in [] to use it as a key for a property. Like this:
updateCheckBox() {
Constants.TABS.map(item => {
AsyncStorage.getItem(key) //
.then((res) => {
//item is accessible here, to use item as the key to a property wrap it in []
if(res == 1) {
this.setState({[item]: true});
} else {
this.setState({[item]: false});
}
})
})
}
finally, I found there is no issue in this code, the thing is
updateCheckBox() {
Constants.TABS.map((item) => {
let key = item
AsyncStorage.getItem(key)
.then((res) => {
console.log(item, "item is here", res); //item is visible here
console.log(key) //key is all always undefined
if(res == 1) {
this.setState({item: true})
} else {
this.setState({item:false})
}
})
})
}
key is not visible in method then, which I can not explain, but all in all, my code works.

Vue tags input custom validation

I'm using vue-tags-input component. In its docs we can find validation. I'm trying to create validation so valid input must have:
min 3 signs
two numbers
comma between numbers
this is what I have:
validation: [{
classes: 'min-length',
rule: tag => tag.text.length < 3,
},{
classes: 'min-length',
rule: ({ text }) => {
const comma = text.indexOf(',') === -1;
if(comma) {
const arr = text.split(',')
if(arr[0] && arr[1]) {
if(arr[0].typeof === 'number' && arr[1].typeof === 'number') {
return true;
}
}
}
return false;
}
}]
So I'm spliting string to array by ,. In result I should have array with two elements. Then I check if both elemenets are numbers. How ever this not work properly because it treat 111 as valid but it shoudn't.
I've created demo on codesanbox.
To check if comma exists you have to check if indexOf comma not equals -1.
const comma = text.indexOf(",") !== -1;
You have to convert the string to number using Number(string).
if (typeof Number(arr[0]) === "number") {..
You have to return false if validation succeeds and true if there is an error,
you are doing the opposite.
The complete code will be:
{
classes: "custom",
rule: ({ text }) => {
const comma = text.indexOf(",") !== -1;
if (comma) {
const arr = text.split(",");
if (arr[0] && arr[1]) {
if (typeof Number(arr[0]) === "number" && typeof Number(arr[1]) === "number") {
return false;
}
}
}
return true;
}
}
A shorter regex rule will be:
{
classes: "custom",
rule: ({ text }) => {
return !text.match(/^\d+,\d+$/);
}
}

FlatList single select cell

I followed the example from official docs, here is how to implement multiselection feature:
state = { selected: (new Map(): Map<string, boolean>) };
onPressItem = (id) => {
this.setState((state) => {
const selected = new Map(state.selected);
selected.set(id, !selected.get(id));
return { selected };
});
};
I'm struggling with making it single select though. It's easy to return new Map with false values anytime cell is tapped, but that means the cell cannot be deselected by another tap on it, which is the desired feature in my case.
onPressItem = (id) => {
this.setState((state) => {
const selected = new Map();
selected.set(id, !selected.get(id));
return { selected };
});
};
How would you implement it? Should I use lodash to iterate over the Map to find the one that already is true and change its value (now sure how to iterate over Map though), or maybe there is some better approach I am missing right now?
EDIT
Iterating over elements of the selected Map seems to be a really ugly idea, but it is simple and it actually works. Is there any better way to do it that I am missing out on?
onPressItem = (id: string) => {
this.setState((state) => {
const selected = new Map(state.selected);
selected.set(id, !selected.get(id));
for (const key of selected.keys()) {
if (key !== id) {
selected.set(key, false);
}
}
return { selected };
});
};
Thanks in advance
You can just set only one value instead of a map like this
onPressItem = (id) => {
this.setState((state) => {
const selected = selected === id ? null : id;
return { selected };
});
};
I had the same issue, my solution was:
_onPressItem = (id: string) => {
// updater functions are preferred for transactional updates
this.setState((state) => {
// copy the map rather than modifying state.
const selected = new Map(state.selected);
// save selected value
let isSelected = selected.get(id);
// reset all to false
selected.forEach((value, key) => {
selected.set(key, false);
});
// then only activate the selected
selected.set(id, !isSelected);
return { selected };
});
};