react native - change prop state depending on number of buttons selected - javascript

I am using a nightlight button library: react-native-selectmultiple-button
In this library there is a prop selected
Description: Type:Boolean. Default is false. The selected prop determines whether the button is selected and highlighted
Is there a way I can change the state of "selected" prop, depending on number of buttons selected?
For example, if I select more than 5 buttons, I want other buttons to be unselectable.
constructor(props) {
super(props)
this.state = {
numberOfbuttonsSelected:0
}
}
{
if(this.state.numberOfbuttonsSelected <5){
<SelectMulipleButton
selected={true}/>}
else{<SelectMulipleButton
selected={false}/>
}
}
The code above won't work any comments or advise would be really appreciated :)
This is the new code:
<View style={{ flexWrap: 'wrap', flexDirection: 'row',backgroundColor:'gray',paddingTop:10,paddingLeft:6,paddingRight:0,borderColor:'white', borderWidth:1}}>
{
multipleData.map(
(interest) =>
<SelectMultipleButton
key={interest}
buttonViewStyle={{
borderRadius: 0,
height: 40,
width: 110,
}}
textStyle={{
fontSize: 15,
}}
highLightStyle={{
borderColor: 'white',
backgroundColor: 'transparent',
textColor: 'white',
borderTintColor: 'white',
backgroundTintColor: '#6AAAC6',
textTintColor: 'white',
}}
multiple={true}
value={interest}
selected={this.state.multipleSelectedData.includes(interest)}
singleTap={valueTap => this.trackSelection(valueTap)} />
)
}
</View>
</ScrollView>

Sorry for the delay in replying. Please see my example component below. I have included explanations in comments inline in the code. Please reach out if you need further help.
export class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
numberOfbuttonsSelected: 0,
multipleSelectedData: []
};
}
//This method is what you mainly need
trackSelection = value => {
if (!this.state.multipleSelectedData.includes(value)) { //This checks if the value already exists in the checked buttons list
if (this.state.numberOfbuttonsSelected < 5) { //Here we check if the number of selected buttons has exceeded the specified number
this.state.multipleSelectedData.push(value);
this.setState({
numberOfbuttonsSelected: this.state.numberOfbuttonsSelected + 1
});
} //else do nothing. Effectively, we are disabling the click on the button.
} else { //we are simply toggling the selection here
this.state.multipleSelectedData.splice(
this.state.multipleSelectedData.indexOf(value), 1
);
this.setState({
numberOfbuttonsSelected: this.state.numberOfbuttonsSelected - 1
});
}
};
render() {
return (
//Customize your render function. I just included one button as an example.
<View>
<SelectMultipleButton
multiple={true}
value={interest} //"interest" is just an example value. Change it according to your requirements for each button.
selected={this.state.multipleSelectedData.includes(interest)}
singleTap={valueTap => this.trackSelection(valueTap)} //valueTap is supposed to be the "value" prop's value for each
//button according to the lib's documentation, but if you're not comfortable using valueTap, you can
//simply pass "interest" (or your own custom value for the particular button) into the trackSelection() method
/>
</View>
);
}
}
EDIT
I went through the code in the lib and the onPress function in the SelectMultipleButton component is why your multiple selection still works:
<TouchableWithoutFeedback
onPress={() => {
if (this.props.multiple) {
this.setState({ selected: !this.state.selected })
this.props.singleTap(this.props.value)
} else {
if (!this.state.selected) {
this.setState({ selected: !this.state.selected })
this.props.singleTap(this.props.value)
}
}
}
}>
I know it's not a good idea to modify library files, but in this case, instead of using the whole lib, you can copy over this file to your project (don't remove the author credit at the top of this file) and add a prop selectable to it and modify the onPress thus:
<TouchableWithoutFeedback
onPress={() => {
if (this.props.multiple) {
if(this.props.selectable) {
this.setState({ selected: !this.state.selected })
this.props.singleTap(this.props.value)
}
} else {
if (!this.state.selected) {
this.setState({ selected: !this.state.selected })
this.props.singleTap(this.props.value)
}
}
}
}>
Pass the prop thus:
<SelectMultipleButton
multiple={true}
value={interest}
selectable={this.state.multipleSelectedData.includes(interest) || this.state.numberOfbuttonsSelected < 5}
selected={this.state.multipleSelectedData.includes(interest)}
singleTap={valueTap => this.trackSelection(valueTap)}
/>
This should solve your problem.

Related

Switching between dynamic Pickers crashes app

I'm new to React Native and need to dynamically populate a Picker depending on the value of a selected Radio Button. For example, in the following code if you select "Animals" the Picker would contain "Dog", "Cat", and "Fish" and if you selected "People" the Picker would contain "Will Smith".
However, if I select "Cat" or "Fish" from Animals and then click on the People radio button, the app crashes. When debugging I saw I was getting an Array Index Out of Bounds Exception, and I'm guessing it's probably because I'm selecting index 1 or 2 in the Animals array, but there is only 1 item in the People array. It must be trying to get index 1 or 2 from the People array, but that is obviously out of bounds.
The behavior I need is for the Picker to go back to the default option "Select one" whenever I change to a different radio button.
import React, { Component } from 'react';
import { View, Picker } from 'react-native';
import RadioGroup from 'react-native-radio-buttons-group';
// Constants for currently selected radio button
const ANIMALS = 0
const PEOPLE = 1
// Constant for when nothing is selected in the Picker
const NOTHING = 'nothing'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
selectedVal: NOTHING, // Currently selected item
radioType: ANIMALS, // Currently selected radio button
radioValues: [
{
type: ANIMALS,
label: "Animals",
},
{
type: PEOPLE,
label: "People",
},
]
}
}
getCategories = (type) => {
if (type === ANIMALS) {
return [
"Dog", "Cat", "Fish"
]
} else if (type === PEOPLE) {
return [
"Will Smith"
]
}
}
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<RadioGroup
flexDirection="row"
radioButtons={this.state.radioValues}
onPress={(data) => {
this.setState({
radioValues: data
})
let selected = this.state.radioValues.find(e => e.selected === true)
if (selected.type === ANIMALS) {
this.setState({ radioType: ANIMALS })
} else if (selected.type === PEOPLE) {
this.setState({ radioType: PEOPLE })
}
}} />
<Picker
selectedValue={this.state.selectedVal}
style={{ height: 50, width: 250 }}
onValueChange={(itemValue) =>
this.setState({ selectedVal: itemValue })
}>
<Picker.Item label="<Select one>" value={NOTHING} />
{
this.getCategories(this.state.radioType).map((categoryName, index) => {
return <Picker.Item label={categoryName} value={categoryName} key={index} />
})
}
</Picker>
</View>
)
}
}
This is what the app looks like:
I tried manually setting this.state.selectedVal back to NOTHING inside of componentDidUpdate() but the app still crashes if I select "Cat" or "Fish" from the Animals view and then switch to People. Strangely it works when I run it from a new Expo project, but not with this project where I used the react-native CLI.
Does anyone know how to fix the array index out of bounds exception?

changing the color of card in material ui conditionally?

const styles = {
card: {
minWidth: 240,
color: 'green'
},
title: {
fontSize: 14
},
pos: {
marginBottom: 12,
padding: 10,
margin: 10
}
};
function handleToggle(colorDecider) {
if (colorDecider)
styles.card.color = 'blue';
else
styles.card.color = 'red';
}
Here, I am trying to change the color of the card using handleToggle function based on the value of colorDecider. But the code doesn't change. Yet I have checked the styles.card.color using console.log, changed color is printed in the console. But, color doesn't actually change in the card
Setting a property somewhere in an object does not magically rerender the related parts of the page. If you need something stateful, move it into the related conponent's state:
class Colorful extends React.Component {
constructor(...props) {
super(...props);
this.state = { color: "red" };
}
changeColor(color) { this.setState({ color }); }
render() {
return <div
style={{ color: this.state.color }}
onClick={() => this.changeColor("blue")}
>Click me!</div>;
}
}
Agree with #jonaswilms but might add that you can also force an update like this:
function handleToggle(colorDecider) {
if (colorDecider)
styles.card.color = 'blue';
else
styles.card.color = 'red';
this.forceUpdate(); // force rerender
}
The above assumes, of course that you have bound the right this value e.g.
<div onClick={handleToggle.bind(this)} />

Getting text from ModelDropdown in react native app development

I would like to know how we can retrieve text from ModelDropdown upon selecting an option:
import ModalDropdown from 'react-native-modal-dropdown';
...
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
category: '',
}
}
updateCategory(newCategory) {
this.setState({
category: newCategory
})
}
....
<ModalDropdown
style={{padding: 20}}
options={['Electronics', 'Furniture']}
textStyle={{fontSize: 20, color: 'black', fontWeight: 'bold',}}
dropdownTextStyle={{fontSize: 20, backgroundColor: '#FFF', color: 'black'}}
defaultValue = 'Select Category'
onSelect={(newCategory) => this.updateCategory(newCategory)}
/>
I m getting index value, like "0" for Electronics and "1" for Furniture.
I would like to get the text corresponding to that index.
Or is there any alternative to ModelDropdown which may help me in accomplish this task?
since you are getting corresponding index so you can easily take value from options array, just set it in state or some global variable.
you can do something like this.
constructor(props) {
super(props);
this.state = {
options:['Electronics', 'Furniture'],
}
}
and then in updateCategory take value like this
updateCategory(newCategory) {
this.setState({
textValue: this.state.options[newCategory]
})
}

React - changing the background of a single span class not working

I am new to React so my apologies if the question, or the thing I am trying to achieve is just weird (and please do tell if there is a better / more logic way to do this).
I am using the List Fabric React component in my React application, which is based on the ListGridExample component which is found here:
https://developer.microsoft.com/en-us/fabric#/components/list
I have set it up but I can't seem to accomplish the following:
When a span class (which is actually an item) in the List component is clicked, I want to change it's background color, to do this I have followed the instructions in the following post:
https://forum.freecodecamp.org/t/react-js-i-need-a-button-color-to-change-onclick-but-cannot-determine-how-to-properly-set-and-change-state-for-that-component/45168
This is a fairly simple example but this changes all my grid cells / span classes to the color blue instead of only the clicked one. Is there a way I can make just the clicked span class change it's background?
The Initial state:
The state after clicking one span class (which is wrong):
Implementation code (ommitted some unecesary code):
class UrenBoekenGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
bgColor: 'red'
}
}
render() {
return (
<FocusZone>
<List
items={[
{
key: '#test1',
name: 'test1',
},
{
name: 'test2',
key: '#test2',
},
{
name: 'test3',
key: '#test3',
},
{
name: 'test4',
key: '#test4',
},
..... up to 32 items
]}
onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
changeColor(item){
this.setState({bgColor: 'blue'});
console.log('clicked item == ' + item.name)
}
_onRenderCell = (item, index) => {
return (
<div
className="ms-ListGridExample-tile"
data-is-focusable={true}
style={{
width: 100 / this._columnCount + '%',
height: this._rowHeight * 1.5,
float: 'left'
}}
>
<div className="ms-ListGridExample-sizer">
<div className="msListGridExample-padder">
{/* The span class with the click event: */}
<span className="ms-ListGridExample-label" onClick={this.changeColor.bind(this, item)} style={{backgroundColor:this.state.bgColor}}>{`item ${index}`}</span>
<span className="urenboeken-bottom"></span>
</div>
</div>
</div>
);
};
}
I now have attached the click event to the span class itself but I would think it is way more logic to have the click event on the item(s) (array) itself, however I could not find a way to achieve this either.
----UPDATE----
#peetya answer seems the way to go since #Mario Santini answer just updates a single cell, if another cell is clicked then the previous one returns back to normal and loses it's color.
So what I did is adding the items array to the state and adding the bgColor property to them:
this.state = {
items: [
{
key: '#test1',
name: 'test1',
bgColor: 'blue',
},
{
name: 'test2',
key: '#test2',
bgColor: 'blue',
},
{
name: 'test3',
key: '#test3',
bgColor: 'blue',
},
{
name: 'test4',
key: '#test4',
bgColor: 'blue',
},
],
}
Now in my List rendering I have set the items to the state items array and added the onClick event in the _onRenderCell function:
render() {
return (
<FocusZone>
<List
items={this.state.items}
getItemCountForPage={this._getItemCountForPage}
getPageHeight={this._getPageHeight}
renderedWindowsAhead={4}
onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
_onRenderCell = (item, index) => {
return (
<div
className="ms-ListGridExample-tile"
data-is-focusable={true}
style={{
width: 100 / this._columnCount + '%',
height: this._rowHeight * 1.5,
float: 'left'
}}
>
<div className="ms-ListGridExample-sizer">
<div className="msListGridExample-padder">
<span className="ms-ListGridExample-label"
onClick={this.onClick(item.name)}
style={{backgroundColor: item.bgColor}}
>
{`item ${index}`}
</span>
<span className="urenboeken-bottom"></span>
</div>
</div>
</div>
);
};
The problem is that I can't add the onClick event in the _onRenderCell function as this will give the following error:
I want to keep the Fabric List component as it also has functions for rendering / adjusting to screen size, removing the list component entirely and just replacing it with what #peetya suggested works:
render() {
<div>
{this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
</div>
}
But this will also remove the List component functionality with it's responsive functions.
So my last idea was to just replace the items of the List with the entire onClick div and removing the _onRenderCell function itself, but this makes the page blank (can't see the cells at all anymore..):
render() {
return (
<FocusZone>
<List
items={this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
getItemCountForPage={this._getItemCountForPage}
getPageHeight={this._getPageHeight}
renderedWindowsAhead={4}
// onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
I thought that perhaps the css ms-classes / div's should be in there as well because these have the height/width properties but adding them (exactly as in the _onRenderCell function) does not make any difference, the page is still blank.
The problem is that you are storing the background color in the state of the Grid and assign this state to every element of the grid, so if you update the state, it will affect every element. The best would be if you create a separate component for the Grid elements and store their own state inside there or if you want to use only one state then store the items array inside the state and add a new bgColor attribute for them so if you want to change the background color only for one item, you need to call the setEstate for the specific object of the items array.
Here is a small example (I did not tested it):
class UrenBoekenGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
key: '#test1',
name: 'test1',
bgColor: 'blue',
},
],
};
}
onClick(name) {
this.setState(prevState => ({
items: prevState.items.map(item => {
if (item.name === name) {
item.bgColor = 'red';
}
return item;
})
}))
}
render() {
<div>
{this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
</div>
}
}
Actually you are changing the color of all the span elements, as you set for each span the style to the state variable bgColor.
Insteas, you should save the clicked item, and decide the color based on that:
this.state = {
bgColor: 'red',
clickedColor: 'blue
}
In the constructor.
Then in the click handler:
changeColor(item){
this.setState({selected: item.name});
console.log('clicked item == ' + item.name)
}
So in the renderer (I just put the relevant part):
<span ... style={{backgroundColor: (item.name === this.state.selected ? this.state.clickedColor : this.state.bgColor)}}>{`item ${index}`}</span>

React list choosing option

I have an location app which can save name of locations.
I am trying to get each saved location a red border by clicking on it.
What it does is changing the border color of all the categories.
How can I apply that?
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
categories: [],
selectedCategories: [],
hidden: true,
checkboxState: true
};
}
toggle(e) {
this.setState({
checkboxState: !this.state.checkboxState
})
}
onChange = (event) => {
this.setState({ term: event.target.value });
}
addCategory = (event) => {
if (this.state.term === '') {
alert('Please name your category!')
} else {
event.preventDefault();
this.setState({
term: '',
categories: [...this.state.categories, this.state.term]
});
}
}
render() {
return (
<div className="categories">
<h1>Categories</h1>
<div className='actions'>
<button className="delete" onClick={this.deleteCategory}>Delete</button>
<button className="edit" onClick={this.editCategory}>Edit</button>
</div>
<p>To add new category, please enter category name</p>
<form className="App" onSubmit={this.addCategory}>
<input value={this.state.term} onChange={this.onChange} />
<button>Add</button>
</form>
{this.state.categories.map((category, index) =>
<button
key={index}
style={this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' }}
checked={this.state.isChecked}
onClick={this.toggle.bind(this)}>
{category}</button>
)}
</div >
);
}
}
I want to be able to control each selected category seperatly, to be able to delete and edit theme as well.
You can set the state based on index and retrieve the similar way,
Code:
{this.state.categories.map((category, index) =>
<button
key={index}
id={`checkboxState${index}`}
style={!this.state[`checkboxState${index}`] ?
{ borderColor: '' } : { border: '2px solid red' }}
checked={this.state.isChecked}
onClick={this.toggle}>
{category}</button>
)}
You can see how I am checking the state dynamically this.state[`checkboxState${index}`] and also I have assigned an id to it.
In toggle method:
toggle = (e) => {
const id = e.target.id;
this.setState({
[id]: !this.state[id]
})
}
FYI, this is a working code, you can see it
https://codesandbox.io/s/vy3r73jkrl
Let me know if this helps you :)
Here's a really bad example using react. I'd more than likely use this.props.children instead of just cramming them in there. This would allow it to be more dynamic. And instead of using state names we could then just use indexes. But you'll observe, that the parent container decides which child is red by passing a method to each child. On click, the child fires the method from the parent. How you implement it can vary in a million different ways, but the overall idea should work.
class ChildContainer extends React.Component
{
constructor(props)
{
super(props);
}
render() {
let color = this.props.backgroundColor;
return(
<section
className={'child'}
style={{backgroundColor: color}}
onClick={this.props.selectMe}
>
</section>
)
}
}
class Parent extends React.Component
{
constructor(props)
{
super(props)
this.state = {
first : 'Pink',
second : 'Pink',
third : 'Pink',
previous: null
}
this.updateChild = this.updateChild.bind(this);
}
updateChild(name)
{
let {state} = this;
let previous = state.previous;
if(previous)
{
state[previous] = 'Pink';
}
state[name] = 'Red';
state.previous = name;
this.setState(state);
}
render()
{
console.log(this)
return(
<section id={'parent'}>
<ChildContainer
selectMe={() => this.updateChild('first')}
backgroundColor = {this.state.first}
/>
<ChildContainer
selectMe={() => this.updateChild('second')}
backgroundColor = {this.state.second}
/>
<ChildContainer
selectMe={() => this.updateChild('third')}
backgroundColor = {this.state.third}
/>
</section>
)
}
}
class App extends React.Component
{
constructor(props)
{
super(props)
}
render()
{
return(
<section>
<Parent/>
</section>
)
}
}
React.render(<App />, document.getElementById('root'));
You need to track the state of every checkbox, possibly have an array with all currently checked checkboxes.
Then instead of this.state.checkboxState in this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' } you need to check if current category is in the currently checked categories array.
Hope this helps

Categories

Resources