Updating nested state in React Native when mapping over data - react-native

I am mapping over state which displays 3 radio buttons and I only want 1 out of the 3 to be selected at one time and show the selected radio button style - so just a standard behaviour.
The first radio button - isChecked is defaulted to true and I then want to be able to switch the selected styles onPress of the other radio buttons which displays an inner filled circle.
I am getting confused on how to handle the isChecked as true for only the selected Radio Button onPress. I believe I should be using the index of the map to update the state but im unsure on how to go about it.
const [option, setOption] = useState([
{ permission: 'Standard User', isChecked: true },
{ permission: 'Approver', isChecked: false },
{ permission: 'Administrator', isChecked: false },
]);
const RadioButton = ({ checked, onCheck }) => (
<StyledRadioButton onPress={onCheck}>
{checked && <SelectedRadioButton />}
</StyledRadioButton>
);
{option.map(({ isChecked }, i) => (
<RadioButton
onCheck={() =>
setOption(
...prev => {
!prev.isChecked;
},
)
}
checked={isChecked}
/>
))}

Your state is an array of objects which hold a boolean flag. If the user checks a checkbox, then this flag should be set to true while all the others should be set to false. The setter of the state receives an array again, thus we could use the map function again as well as the index argument as you have suggested yourself in your question.
{option.map(({ isChecked }, i) => (
<RadioButton
onCheck={() =>
setOption(
prev => prev.map((opt, index) => ({...opt, isChecked: i === index ? true : false}))
)
}
checked={isChecked}
/>
))}

Related

React-Native Switch uncontrolled using an object doesn't change value

I have a react-native project in which I'm trying to handle the Switch component in a way that I can dynamically set an object with the boolean and its value, but I doesn't respond well:
Initially I use useEffect to process the data:
useEffect(() => {
let lista = {}
Object.entries(planes).forEach(([j, plan]) => {
lista[plan.nombre] = false
})
setSeleccion(lista)
}, []);
Then I loop through my data to load it and create the "card" dynamically with a Switch:
<Switch
offTrackColor="indigo.100"
onTrackColor="indigo.300"
onThumbColor="coolgray.500"
offThumbColor="indigo.50"
size="lg"
onToggle={(val) => toggleSwitch(val, plan.nombre)}
value={seleccion[plan.nombre]}
/>
Then toggle function:
const toggleSwitch = (val, plan) => {
setSeleccion({...seleccion, [plan]: val})
};
Now if I print the new array it shows as expected:
useEffect(() => {
console.log(seleccion)
}, [seleccion]);
But sadly the Switch remains as false visually (just goes back to its false state, so one cant toggle back to off/false).
If I do a toString(seleccion[plan.nombre]) in the render it shows [object Undefined]
Assuming that the Switch that you used is same/from react-native. There shouldn't be a prop called onToggle in Switch like you wrote below.
<Switch
offTrackColor="indigo.100"
onTrackColor="indigo.300"
onThumbColor="coolgray.500"
offThumbColor="indigo.50"
size="lg"
onToggle={(val) => toggleSwitch(val, plan.nombre)}
value={seleccion[plan.nombre]}
/>
instead, you need to use onValueChange
<Switch
offTrackColor="indigo.100"
onTrackColor="indigo.300"
onThumbColor="coolgray.500"
offThumbColor="indigo.50"
size="lg"
onValueChange={(val) => toggleSwitch(val, plan.nombre)}
value={seleccion[plan.nombre]}
/>
You can read more about the props in Switch here

Implementing react-hook-forms and react-select with radio buttons and multi inputs combo

Ready for a complicated one?
Using react-hook-forms and react-select using creatable (user creates multiple inputs on the fly)
I'm trying to implement a form that uses an option on 4 radio buttons, 2 of which reveal multi inputs (inputs that use react-select where the user can create multiple entries, not a dropdown) and trying to keep track of both the radio inputs and the multi inputs in the final useForm() object. I also need to be able to remove them if the user changes their mind or resets the form in total. Right now, the key values of registrationTypes changes when I change radioTwo and enter inputs. I also don't know how to remove user inputs. I'm using Controller to read the entries (although I've heard if you're using native HTML checkbox input, you have to use Register?) . Here's the code:
import styled from 'styled-components'
import Creatable from 'react-select/creatable'
import { Controller } from 'react-hook-form'
import { ErrorRow } from '../util/FormStyles'
import { FormRulesProps } from '../util/formRuleTypes'
import FormError from './FormError'
const registrationTypes = [
{
label: 'Allow anyone with the link to register',
value: 'radioOne',
},
{
label: 'Allow anyone with this email domain to register:',
value: 'radioTwo',
},
{ label: 'Allow anyone with this code to register:', value: 'radioThree' },
{
label: 'Define eligible users manually through eligibilty file.',
value: 'radioFour',
},
]
const RegistrationEligibilty = () => {
const {
control,
reset,
handleSubmit,
formState: { errors },
} = useForm<any>()
const [selectedRadio, setSelectedRadio] = useState({ regType: '' })
const onSubmit = (data: any) => {
console.log(data)
}
const handleSelected = (value: string) => {
setSelectedRadio({ ...selectedRadio, regType: value })
}
return (
<RegistrationEligibiltyContainer>
<form onSubmit={handleSubmit(onSubmit)}>
<FormRow>
<RadioWrapper>
{registrationTypes.map((rt) => (
<Controller
key={rt.value}
control={control}
name="radio"
render={({ field: { onChange, value } }: any) => (
<RadioGroup
// #ts-ignore
value={value}
onChange={(e: any) =>
onChange(e.target.value, handleSelected(rt.value))
}
>
<Radio
name={rt.value}
type="radio"
value={rt.value}
checked={selectedRadio.regType === rt.value}
/>
<TextRow
text={rt.label}
style={{ paddingLeft: '10px' }}
/>
{selectedRadio.regType === 'radioTwo' &&
rt.value === 'radioTwo' && (
<FormRow>
<div style={{ width: '90%' }}>
<Controller
name={rt.value}
control={control}
render={({ field }) => (
<Creatable
{...field}
isMulti
options={field.value}
value={field.value}
placeholder="Select domains"
/>
)}
/>
</div>
</FormRow>
)}
{selectedRadio.regType === 'radioThree' &&
rt.value === 'radioThree' && (
<FormRow>
<div style={{ width: '90%' }}>
<Controller
name={rt.value}
control={control}
render={({ field }) => (
<Creatable
{...field}
isMulti
options={field.value}
value={field.value}
placeholder="Select codes"
/>
)}
/>
</div>
</FormRow>
)}
</RadioGroup>
)}
/>
))}
</RadioWrapper>
</FormRow>
</FormContent>
</form>
</RegistrationEligibiltyContainer>
)
}
export default RegistrationEligibilty
The result looks like this:
https://imgur.com/a/6oGhqRb
I also need to be able to remove them if the user changes their mind or resets the form in total
If the component is unmounted, the value of the component is not included in the result. Make sure your Creatable is unmounted when the relevant radio is not selected

React native updating multiple child component states

I am making a minesweeper app in react native as a personal project to try and learn the concepts. The problem I am having is trying to update multiple children objects at the same time. I'm not sure if this is the best practice.
I have 3 components Game > Grid > cell. The Game component takes care of the logic of the game such as timer/win condition/lose condition. The grid is a collection of cells and determines what each cell should be.
Game component:
render() {
return (
<Grid
action={this.props.action}
firstClick={this.state.firstClick}
/>
);
}
I pass the state firstClick to the grid component which is equal to true. meaning when I tap the first cell I want it to reveal all surrounding neighbors while first click is equal to true.
Grid Component:
_renderItem = ({ item }) => (
<Cell
item={item}
neighbours={this.state.neighbours[item.key.substring(0,1)][item.key.substring(1,2)]}
firstClick={this.props.firstClick}
/>
);
render() {
return (
<FlatList
data={this.state.flatGrid}
renderItem={this._renderItem}
keyExtractor={this._keyExtractor}
numColumns={10}
/>
);
}
The grid is a flatlist of cells and the grid knows the neighboring cells of each cell when its tapped. The problem I am having is I'm not sure where I should update the state of the tapped cell and its neighboring cells. I would like the isHidden state to be set to false for the cell plus all of its neighbors.
Should it be handled in the grid component or cells component? I'm not sure how to tackle this without breaking encapsulation.
These are the states in my cell Component
constructor(props) {
super(props);
this.state = {
isFlagged: false,
isHidden: true,
value: '',
id: ''
};
Thanks in advance!
Here is something which can make life easier for you.
First of all, your cell should be a PureComponent and you should never store the state in your cell. The sole reason being the cell will be reused at some point and your entire screen might start behaving randomly. You can store the state of each cell model in the data source.
Finally, you should pass a callback function as props to your cell. When the cell is tapped, then calculate the neighboring cells in your Grid component. When this is calculated, then change the appropriate cell models accordingly.
Example
class Grid extends React.Component {
state = {
dataSource: [{
isFlagged: false,
isHidden: true,
value: '1',
id: '1',
}, {
isFlagged: false,
isHidden: true,
value: '2',
id: '2',
}],
};
onCellClicked = (item) => {
// Find neighbouring items and replace the modified items in the state in this function
}
_renderItem = (item) => {
return <Cell onClick={() => this.onCellClicked(item)} item={item}>;
}
render() {
return (
<FlatList data={this.state.dataSource} renderItem={this._renderItem}/>
);
}
}

Flat List gets hidden once the state gets changed in react native

I am having a FlatList view with checkbox for each item.when i press a checkbox the state gets changed from false to true at that time the list gets hidden.Does anyone has come through this strange behaviour.
<FlatList
data={this.state.branches}
renderItem={({ item }) => {
{ tempCheckValues[item._id] = false; }
return (
<ListItem avatar key={item._id}>
<Left>
<CheckBox
checked={item.checked}
onPress={this.toggleCheckbox.bind(this, item._id)}
/>
</Left>
<Body>
<Text>{item.branch_name}</Text>
<Text note>{item.formatted_address}</Text>
</Body>
</ListItem>
);
}
}
/>
Check Box Toggle:
toggleCheckbox = (id) => {
const changedCheckbox = this.state.branches.find((branch) => branch._id === id);
console.log('changedCheckbox', changedCheckbox);
changedCheckbox.checked = !changedCheckbox.checked;
const checkboxes = Object.assign({}, this.state.checkboxes, changedCheckbox);
this.setState({ branches: checkboxes });
}
You are mutating the state can be the reason for the problem. It would better to clone the branches state and update the cloned branches and finally setState.
const clonedBranches = JSON.parse(JSON.stringify(this.state.branches));
const updatedBranch = clonedBranches.find(({_id}) => _id === id)
updatedBranch=!updatedBranches.checked;
this.setState({branches: clonedBranches})
inside toggleCheckbox
this.setState({ branches: checkboxes });
but you are getting data in FlatList with branches
<FlatList
data={this.state.branches}
When you call toggleCheckbox, branches array is updated.
Got it?

How to display a default value on a page in react Native

I am displaying content on the page on click of a drop-down button. I want to set some default value on the page irrespective of the dropdown button click and it should change when clicked on the drop-down button. Is there any way of doing it. Please Help.
My dropdown has 4 items. I want to display the first item as default. I have written an onclick event for it and for every onclick, I am fetching a service.
async onChangeText(apiurl) {
this.setState({
refreshing: false
});
let paramUrl;
this.state.data.forEach(eachUrl => {
if (apiurl === eachUrl.displayName) {
paramUrl = eachUrl.apiurl;
}
});
const res = await getResult(paramUrl);
this.setState({ listItems: res.data });
}
My dropdown looks like this
<Dropdown
data={this.state.data}
label='Select'
fontSize={15}
valueExtractor={({ displayName }) => displayName}
itemPadding={10}
onChangeText={this.onChangeText.bind(this)}
/>