React Native - Updating state upon pressing a button - javascript

I currently have two buttons (No, Yes)( component imported from native-base package) that when pressed should update the state with either 0 or 1 respectively and also toggle between true or false to notify if this field has been filled (by default, neither of the two will be pressed, hence set to false).
I have a handleOnClick() function bound to the "No" button with a debugger to test if I actually do hit it, but once inside this function, I'm not sure how to grab any info for associated components (i.e. the "No" text within the Text component) so I can perform logic to check if "No" or "Yes" was pressed.
If this was done in pure React, I know I can access certain data attributes that I add to DOM elements or traverse the DOM, but I'm not sure how to do this in React Native or if I'm able to add custom props to a built in component that I can then access.
class Toggle extends Component {
constructor() {
super()
this.state = {
selectedOption: '',
isFilled: false
}
this.checkField = this.checkField.bind(this)
this.handleOnClick = this.handleOnClick.bind(this)
}
checkField() {
console.log(this)
// debugger
}
handleOnClick(ev) {
debugger
console.log("I was pressed")
}
render() {
const options = this.props.inputInfo.options //=> [0,1]
const optionLabels = this.props.inputInfo.options_labels_en //=>["No","Yes"]
return (
<View style={{flexDirection: 'row'}}>
<View style={styles.row} >
<Button light full onPress={this.handleOnClick}><Text>No</Text></Button>
</View>
<View style={styles.row} >
<Button success full><Text>Yes</Text></Button>
</View>
</View>
)
}
}

If you want to pass information into function, you can pass it when it is called. In your case, you can call your function from arrow function, like so:
<View style={{flexDirection: 'row'}}>
<View style={styles.row} >
<Button light full onPress={() => this.handleOnClick('No')}>
<Text>No</Text>
</Button>
</View>
<View style={styles.row} >
<Button success full><Text>Yes</Text></Button>
</View>
</View>
And in your function
handleOnClick(text) {
debugger
console.log(`${text} pressed`)
}

Have you tried:
render() {
const options = this.props.inputInfo.options //=> [0,1]
const optionLabels = this.props.inputInfo.options_labels_en //=>["No","Yes"]
return (
<View style={{flexDirection: 'row'}}>
<View style={styles.row} >
<Button light full onPress={() => this.handleOnClick('No')}><Text>No</Text></Button>
</View>
<View style={styles.row} >
<Button success full onPress={() => this.handleOnClick('Yes')}><Text>Yes</Text></Button>
</View>
</View>
)
}
and
handleOnClick(word) {
this.setState({ selectedOption: word, isFilled: true })
}

Related

Need to update state when number of rows is unknown

I doubt this is possible but I'd like to hear your thoughts.
I am getting an array which the number of values are unknown.
And I am using a for loop to spread it in a state:
const { letters,number } = route.params
const [arr,setArr] = useState([])
useEffect(() => {
var eachArr = []
setArr([])
for (let i = 1; i < Number(number) + 1; i++) {
eachArr.push(i)
}
setArr(eachArr)
}, [])
Now I use the state to map through to render accordingly
return (
<View style={styles.container}>
<ScrollView>
{
arr.length > 0 && arr.map(num => (
<View style={styles.placeContainer} key={num}>
<View style={styles.place}>
<Text style={{ fontWeight:'bold',letterSpacing:1 }}>{num} {letters}</Text>
<Feather name="check-circle" size={24} color="grey" />
</View>
<View style={{ marginVertical:10 }}>
<Progress color={colors.purple} progress={1} width={null} borderWidth={0} />
</View>
<View style={styles.button}>
<Text style={{ color:colors.white,fontFamily:'viga' }}> this {letters}</Text>
</View>
<View style={{...styles.button,backgroundColor:colors.lightPurple}}>
<Text style={{ color:colors.white,fontFamily:'viga' }}> this {letters}</Text>
</View>
</View>
))
}
</ScrollView>
</View>
)
I now have a problem. I have two buttons on each of the mapped element/component as you can see above.
I need to listen for a click on one and deactivate the clicked button and then activate the other button.
The problem is that I do not know how to set/update a state based on the which button is clicked since the number of rows are unknown.
I know I can call a function:
const callFunc = (val) -> {
console.log(val)
}
And call the function when a respective button is clicked and pass the val or sth. While this solves some part of the issue, how do I then deactivate it and active the new button when I didn't set a state for it

React Native, how to change display of touchable opacity to visible when clicking on another, and invisible when you click again?

As the title suggests, I am struggling to find a way to make my touchable opacities have a display of none by default (well, I suppose that is easy enough with a styling of display: none), but I'm not able to figure out how to toggle that using a touchable opacity.
In my head, the logic is to have the state change from true to false onpress, and false is visible while true is invisible. However, I can't muster up the knowledge to code it out. Here is what I have so far, more info below code:
import React, {useState} from 'react';
import { KeyboardAvoidingView, StyleSheet, Text, View, TextInput, TouchableOpacity, Keyboard, ImageBackground } from 'react-native';
import Task from './components/task';
const plus = {uri: 'https://media.discordapp.net/attachments/639282516997701652/976293252082839582/plus.png?width=461&height=461'};
const done = {uri: 'https://media.discordapp.net/attachments/736824455170621451/976293111456231434/done.png?width=461&height=461'};
const exit = {uri: 'https://media.discordapp.net/attachments/639282516997701652/976293251759898664/exit.png?width=461&height=461'};
const cog = {uri: 'https://media.discordapp.net/attachments/639282516997701652/976293672884789288/cog.png?width=461&height=461'};
function App() {
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const buttons = {plus, done, exit, cog}
const [selected, setSelected] = useState(buttons.plus)
const [done, setDone] = useState(buttons.plus);
const openMenu = () => {
setSelected(buttons.exit);
//Make 'done' and 'cog' TouchableOpacity(s) visible. Click again and they become invisible.
//this.setState({ visible : !this.state.visible}) This makes visible invisible if not invisible already.
//visible should be the name of a state.
{/*handleAddTask();*/}
}
const handleAddTask = () => {
setDone(buttons.done);
Keyboard.dismiss();
setTaskItems([...taskItems, task]); {/*Puts out everything in the taskItems as a new array then appends the new task to it */}
setTask(null);
setSelected(buttons.plus) //Makes exit button go back to plus because it shows that its finished. DO same for display none for extended buttons when I figure it out.
}
const completeTask = (index) => {
let itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
}
return (
<View style={styles.container}>
{/*Tasks*/}
<View style={styles.tasksWrapper}>
<Text style={styles.sectionTitle}>Tasks</Text>
<View style={styles.items}>
{/*This is where tasks go*/}
{
taskItems.map((item, index) => {
return (
<TouchableOpacity key={index} onPress={() => completeTask(index)}>
<Task text={item} />
</TouchableOpacity>
)
})
}
</View>
</View>
{/*Write a task*/}
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={styles.writeTaskWrapper}>
<TextInput style={styles.input} placeholder={'Write a task'} value={task} onChangeText={text => setTask(text)}/>
<View style={styles.buttonRow}>
<TouchableOpacity onPress={() => openConfig()}>
{/* Opens config for creation (i.e. calendar, timer, repetition, etc). */}
<View style={styles.addWrapper}>
<ImageBackground source={buttons.cog} alt='button' resizeMode="cover" style={styles.plusButton} />
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleAddTask()}>
{/* Done (check) button which does handleAddTask. */}
<View style={styles.addWrapper}>
<ImageBackground source={buttons.done} alt='button' resizeMode="cover" style={styles.plusButton} />
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => openMenu()}>
{/* Onpress opens menu, then shows exit button to go back and revert menu to one button. */}
<View style={styles.addWrapper}>
<ImageBackground source={selected} alt='button' resizeMode="cover" style={styles.plusButton} />
</View>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</View>
);
}
The three touchable opacities at the bottom are what I'm trying to change. The first two should by default be invisible, and I think I can do that by assigning useState(false) for them and false should make their display none. Then on the click of the third touchable opacity, it changes their previous state => !previous state.
However, I'm not sure how to code this out and am quite confused. Any help is appreciated. Thanks!
This can be done using conditional rendering. You will either need a state for each of the buttons or a state that holds an array.
Here is a minimal example which works in general.
function App() {
const [isAVisible, setAVisible] = useState(true);
const [isBVisible, setBVisible] = useState(false);
return (
<View>
{isAVisible && (
<TouchableOpacity onPress={() => setIsBVisible(prev => !prev)}}>
<Text>Toggle Visibility of B</Text>
</TouchableOpacity>
)}
{isBVisible && (
<TouchableOpacity onPress={() => setIsAVisible(prev => !prev)}}>
<Text>Toggle Visibility of A</Text>
</TouchableOpacity>
)}
</View>
)
}
The above creates two TouchableOpacity. The first toggles the visibility of the second one, and the second one toggles the visibility of the first one. Notice, that the default state of the second one is set to false, thus it will be not be visible on first render.

How to pass Button component's title into a function in React Native

I want to pass the title of a React Native Button component into a neighbouring function. I am using React Native functional components only for this application.
Here's the component. I would like to pass the title of the button pressed by the user, which will be either 'English' or 'Arabic', into the function submitLanguageSelection so that I can then save that value into useLocalStorage(), a custom hook I wrote to handle AsyncStorage, so that the next time the user uses the app, their language choice will be persisted, and they will not be shown the ChooseYourLanguageScreen again.
All help appreciated, thank you.
const ChooseYourLanguageScreen = ({ navigation }) => {
const [saveData, storedValue, errorMessage] = useLocalStorage();
const [userSelectedLanguage, setUserSelectedLanguage] = React.useState('');
const submitLanguageSelection = () => {
//TODO: receive params from onPress
//TODO: save the data locally
//TODO: navigate to welcome screen
};
return (
<View style={styles.container}>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text style={styles.text}>This is the Choose Your Language Screen</Text>
<View style={styles.buttons}>
<View>
<Button
title={'English'}
onPress={() => submitLanguageSelection()}
/>
</View>
<View>
<Button title={'Arabic'} onPress={() => submitLanguageSelection()} />
</View>
</View>
</View>
);
};
You can simply pass it to the function
<Button title={'Arabic'} onPress={() => submitLanguageSelection('Arabic')} />
And access like below
const submitLanguageSelection = (language) => {
console.log(language);
};
Getting data from a sibling component is an anti-pattern.
The source of the knowledge of the language options is the ChooseYourLanguageScreen component (as seems from your snippet), so it should hold the list of available languages. Having that, you can just iterate through them and render the appropriate components:
<View style={styles.buttons}>
{languages.map((language) => (
<View key={language}>
<Button
title={language}
onPress={() => submitLanguageSelection(language)}
/>
</View>
))}
</View>

Natigate to different screes within same page in React Native

I have made a main screen in which I have added three button in the header, on pressing I want to open three different screens respectively but its not working.
Here's what I've tried:
constructor(props) {
super(props);
this.state = {
initialstate: 0, //Setting initial state for screens
};
}
render(){
return(
<View style={styles.container}>
<TouchableOpacity onPress={() => this.setState({ initialstate: 0})}>
<Image source={require('../../assets/add.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 1})}>
<Image source={require('../../assets/request.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 2})}>
<Image source={require('../../assets/send.png')}
resizeMode="contain"/>
</TouchableOpacity>
{this.state.initialstate == 0 ? ( <RequestComp/> ) : ( <TopUpComp/> ) } //Over Here when I use the Third Screen like " : <SendComp/> " it gives me JXS error says "EXPECTED }"
</View>
The first problem is that you have an initialState state variable that is only updated by the first buttons and the other two are setting cardState so even if the ternary statement was formatted correctly it wouldn't have worked either way
But aside from this problem I don't recommend using a ternary for what you're trying to do, because the conditions become difficult to read.
There are multiple ways of doing this, but I like the approach of the accepted answer here React render various components based on three logic paths). The idea is to create an object that holds a mapping of strings to components. Then you can conditionally render an item based on the current key value.
Here's an example of how you could refactor your code to use this approach:
const tabComponents = {
request: <RequestComp />,
topUp: <TopUpComp />,
send: <SendComp />,
};
class CustomTabs extends React.Component {
constructor(props) {
super(props);
this.state = {
cardstate: 'request', // Setting initial state for screens
};
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => this.setState({ cardstate: 'request' })}>
// Button content...
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 'topUp' })}>
// Button content...
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.setState({ cardstate: 'send' })}>
// Button content...
</TouchableOpacity>
{tabComponents[this.state.cardstate]}
</View>
);
}
}

How would I dynamically append duplicate components in react, react-native

I am confused about how to properly dynamically add/create same components on button press for react native. I have used .map(()=>{}) on existing info to create components and then display the results.
Would I have to save each new component into a setstate array, then map that?
I looked a little into refs, but wasn't sure how that was better than just a setstate. The problem I see is if I want to update the value for each component, how would I go about that if their all originally duplicates?
Something along the lines of this:
class SingleExercise extends Component {
constructor(props) {
super(props);
this.state = {
objInfo: this.props.navigation.getParam("obj"),
currentSetSelected: 0
};
this.addSet = this.addSet.bind(this);
}
addSet = () => {
return (
<addSet />
)
}
render() {
const { navigation } = this.props;
return (
<View style={{ flex: 1 }}>
<View style={{ height: 80 }}>
<addSet />
<View>
<Button //here
large={false}
onPress={() => {
this.addSet();
}}
title={"add more"}
/>
</View>
</View>
</View>
);
}
}
const addSet = () => {
return (
<TouchableHighlight>
<View>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
defaultValue={'test'}
onChangeText={(text) => this.setState({text})}
/>
</View>
</TouchableHighlight>
);
}
Here is what I would do:
Every click on addSet button should increment the AddSets counter like this:
<Button
large={false}
onPress={() => {
this.setState({addSetsCounter: this.state.addSetsCounter});
}}
title={"add more"}
/>
After every state update your component will be re-rendered. So now, all you need to do is to forLoop in through that counter and return as many AddSets components as needed. A simple for loop with .push() inside would do.
Inside render, before return place something like that:
let sets =[];
for(let i =0;i<this.state.addSetsCounter;i++){
sets.push(<AddSets key="AddSets-{i}"/>);
}
Then simply render your {sets}.
I cannot fully test that right now, I wrote that from the top of my head, just play with the above, at least I hope it points you in a right direction.

Categories