Creating a checkbox group with React Native - react-native

Good Morning! I am wanting to create a selection box where the user has several options of items to choose from and when clicking on a button, it triggers a function that shows all the values that the user chose in the form of an array, json or even arrays ( hard task).
In the React Native documentation, only simple examples of checkboxes using the component are provided and I wanted to go much further than the documentation provides me. What are the possible solutions to this problem? (from a simpler example to an advanced one) and what (s) ways can I explore this problem in order to solve it in the most practical and uncomplicated way?
Definitions and examples of official documentation:
https://reactnative.dev/docs/checkbox/ (CheckBox)
https://reactnative.dev/docs/button/ (Button)
With this problem, another one came up: build an application where the user selects shopping options (items) and a subtotal is displayed in the lower corner of the application as he selects or deselects the items he is going to buy, and there is also an option to reset the subtotal by returning it to the zero value.
From the problem mentioned at the beginning, what are the possible solutions to create this application previously mentioned in a practical and simple way?

Multi Checkbox example ( Updated with Hook )
export const Example = () => {
const [checkboxes, setCheckboxes] = useState([{
id: 1,
title: 'one',
checked: false,
}, {
id: 2,
title: 'two',
checked: false,
}]);
const onButtonPress = () => {
const selectedCheckBoxes = checkboxes.find((cb) => cb.checked === true);
// selectedCheckBoxes will have checboxes which are selected
}
const toggleCheckbox = (id, index) => {
const checkboxData = [...checkboxes];
checkboxData[index].checked = !checkboxData[index].checked;
setCheckboxes(checkboxData);
}
render(){
const checBoxesView = checkboxes.map((cb, index) => {
return (
<View style={{flexDirection:"row"}}>
<Checkbox
key={cb.id}
checked={cb.checked}
onPress={() => toggleCheckbox(cb.id, index)} />
<Text>{cb.title}</Text>
</View>
);
});
return (
<View>
{ checBoxesView }
<Button onPress={onButtonPress} title="Click" />
</View>
);
}
}

Related

Screen appears to unexpectedly render twice after mounting. React Native

I'm trying to solve an issue I'm having with RN. I'm working with the Philips Hue API. I have one screen that is part of a tab navigation and I have a stack navigation nested inside one of the tabs. I have a screen that shows all lights connected to the hub and when you select one lightbulb it navigates you to a detail screen.
Below is my "All Lights" screen:
<FlatList
data={allLights}
renderItem={({ item, index }) => {
console.log(item, "from light icon");
return (
<TouchableOpacity
onPress={() =>
navigation.navigate("LightDetail", {
lightbulb: item,
lightId: index + 1,
})
}
>
<LightBulb
lightName={item.name}
isOn={item.state.on}
lightId={index + 1}
/>
</TouchableOpacity>
);
}}
/>
You will notice I'm passing in data as 'lightbulb'.
Below is my "Light Detail" screen:
export const LightDetailScreen = ({ route }) => {
const { lightbulb, lightId } = route.params;
console.log(lightbulb.state.sat, lightbulb.state.bri, "from params");
const [lightBulbState, setLightBulbState] = useState({
sat: lightbulb.state.sat,
hue: lightbulb.state.hue,
bri: lightbulb.state.bri,
isOn: lightbulb.state.on,
});
console.log(lightBulbState);
const controlLight = async (lightID, on, hue, sat, bri) => {
console.log(hue, sat, bri, "from control light");
try {
return await huePhil.put(\/lights/${lightID}/state`, {on,sat,bri,hue, }); } catch (err) {console.log(err); } };const toggleLight = async () => {setLightBulbState((prev) => {return { ...prev, isOn: !prev.isOn }; });controlLight(lightId,lightBulbState.isOn,lightBulbState.hue,lightBulbState.sat,lightBulbState.bri ); };const upDateBri = (value) => {setLightBulbState((prev) => {return { ...prev, bri: Math.floor(value) }; });controlLight(lightId,lightBulbState.isOn,lightBulbState.hue,lightBulbState.sat,lightBulbState.bri ); };const upDateSat = (value) => {setLightBulbState((prev) => {return { ...prev, sat: Math.floor(value) }; });controlLight(lightId,lightBulbState.isOn,lightBulbState.hue,lightBulbState.sat,lightBulbState.bri ); };return (<SafeArea><View style={styles.container}><Text>{lightbulb.name} </Text><View style={styles.sliderContainer}><Sliderstyle={{ width: 300, height: 40 }}minimumValue={0}maximumValue={254}minimumTrackTintColor="#FFFFFF"maximumTrackTintColor="#000000"value={lightbulb.state.sat}onValueChange={upDateSat}/><Text>Saturation: {lightBulbState.sat || lightbulb.state.sat}</Text></View><View style={styles.sliderContainer}><Sliderstyle={{ width: 300, height: 40 }}minimumValue={0}maximumValue={254}minimumTrackTintColor="#FFFFFF"maximumTrackTintColor="#000000"value={lightbulb.state.bri}onValueChange={upDateBri}/><Text>Brightness: {lightBulbState.bri || lightbulb.state.bri}</Text></View><View style={styles.btnContainer}><Buttontitle={lightBulbState.isOn ? "currently on" : "currently off"}onPress={toggleLight}/></View></View></SafeArea> );`
The three logs that I have bold faced are logging 3 times each. Here is the data from the logs:
93 71 from params
Object {
"bri": 71,
"hue": 8417,
"isOn": true,
"sat": 93,
}
8417 93 71 from control light
93 71 from params
Object {
"bri": 71,
"hue": 8417,
"isOn": true,
"sat": 0,
}
8417 0 71 from control light
93 71 from params
Object {
"bri": 0,
"hue": 8417,
"isOn": true,
"sat": 0,
}
I would expect to only see the logs once instead I get them 3 times. Also even if it did log more than once I would expect the data to remain the same every time but as you can see on the second iteration I'm losing "sat" and on the third I'm losing "bri"
What could be causing this behavior? I hope someone can point me in the right direction.
Thanks!
It's pretty normal to have re-renders. But if you want to avoid them, such as for performance reasons (which tends to be a good idea), you need to identify why it's re-rendering.
For this, you can use the tool Why Did You Render. It will compare props and see which props have changed. In this case, it's probably lightbulb, because as it's an object, during each render tick lightbulb (old) !== lightbulb (new). This is because object instances are not equal to each other ({} !== {}). You can get around this by passing lightbulb props as individual props (i.e. LightDetailScreen = ({hue, sat, bri, on}) => etc...). You may also need to wrap your LightDetailScreen in React.memo, so that the same component instance is returned when the props are the same.
That said, the (probably) only reason it's re-rendering in the first place is because a higher up component is re-rendering as well, so you could also investigate your root component and see why it's mounting 3 times.
If you'd like more ideas, post more code and maybe we can pinpoint where the extra renders are coming from.

Populte WYSIWYG editor after react native fetch

I am trying to incorporate this WYSIWYG package into my react native project (0.64.3). I built my project with a managed workflow via Expo (~44.0.0).
The problem I am noticing is that the editor will sometimes render with the text from my database and sometimes render without it.
Here is a snippet of the function that retrieves the information from firebase.
const [note, setNote] = useState("");
const getNote = () => {
const myDoc = doc(db,"/users/" + user.uid + "/Destinations/Trip-" + trip.tripID + '/itinerary/' + date);
getDoc(myDoc)
.then(data => {
setNote(data.data()[date]);
}).catch();
}
The above code and the editor component are nested within a large function
export default function ItineraryScreen({route}) {
// functions
return (
<RichEditor
onChange={newText => {
setNote(newText)
}}
scrollEnabled={false}
ref={text}
initialFocus={false}
placeholder={'What are you planning to do this day?'}
initialContentHTML={note}
/>
)
}
Here is what it should look like with the text rendered (screenshot of simulator):
But this is what I get most of the time (screenshot from physical device):
My assumption is that there is a very slight delay between when the data for the text editor is actually available vs. when the editor is being rendered. I believe my simulator renders correctly because it is able to process the getNote() function faster.
what I have tried is using a setTimeOut function to the display of the parent View but it does not address the issue.
What do you recommend?
I believe I have solved the issue. I needed to parse the response better before assigning a value to note and only show the editor and toolbar once a value was established.
Before firebase gets queried, I assigned a null value to note
const [note, setNote] = useState(null);
Below, I will always assign value to note regardless of the outcome.
if(data.data() !== undefined){
setNote(data.data()[date]);
} else {
setNote("");
}
The last step was to only show the editor once note no longer had a null value.
{
note !== null &&
<RichToolbar
style={{backgroundColor:"white", width:"114%", flex:1, position:"absolute", left:0, zIndex:4, bottom: (toolbarVisible) ? keyboardHeight * 1.11 : 0 , marginBottom:-40, display: toolbarVisible ? "flex" : "none"}}
editor={text}
actions={[ actions.undo, actions.setBold, actions.setItalic, actions.setUnderline,actions.insertLink, actions.insertBulletsList, actions.insertOrderedList, actions.keyboard ]}
iconMap={{ [actions.heading1]: ({tintColor}) => (<Text style={[{color: tintColor}]}>H1</Text>), }}
/>
<RichEditor
disabled={disableEditor}
initialFocus={false}
onChange={ descriptionText => { setNote(descriptionText) }}
scrollEnabled={true}
ref={text}
placeholder={'What are you planning to do?'}
initialContentHTML={note}
/>
}
It is working properly.

In react-native-google-places-autocomplete , there is a method to set the inputs initial value, but it doesn't trigger the search query

I am trying to pass a saved search term string to prefill into google places autocomplete's input field. The setAddressText method they offer successfully prefills the input, but it does not trigger the search, so no dropdown options open. Only once you type something does the query run, so its obviously listening for an onChangeText event before it runs the query. I therefore don't see the point in the setAddressText method, if you then have to type again anyway. Surely there must be a way to trigger the search, without having to add / takeaway characters manually with the keyboard from the existing search term.
const { googleApiKey } = config
const ref = useRef()
useEffect(() => {
ref.current?.setAddressText(initialValue)
}, [])
return (
<View sx={{ flex: 1 }}>
<GooglePlacesAutocomplete
ref={ref}
placeholder={placeholder}
listViewDisplayed="true"
fetchDetails={true}
textInputProps={{
autoFocus: true,
...sx
}}
onPress={(data, details = null) => {
console.log("data", data, "details", details)
}}
query={{
key: googleApiKey,
language: "en",
components: "country:gb",
types: types ? types : null
}}
/>
</View>
)
}
Any wisdom on this would be greatly appreciated!
Full Disclosure: I maintain this library.
This is not currently supported by the library.
There is a feature request open here.

React Native: Checkbox List Structure

A user object has an array prop schools that references one or more school objects. I would like to use a <List> with <CheckBox> to mutate the schools array.
I load the user object into the view, and I load the listOfSchools (from the application state) to generate the checkbox list:
<List data={listOfSchools} keyExtractor={ item=> item._id } renderItem={({item})=>renderItem(item)} />
The renderItem function:
const renderItem = (school) => {
return <ListItem
title={school.name}
accessory={()=>renderAccessory(school)}
/>
};
The renderAccessory function:
const renderAccessory = (school) => {
return <CheckBox checked={() => checkSchool(school._id)} onChange={()=>changeSchool(school._id)} />
}
The checkSchool function returns boolean on if the school._id is referenced in the user.schools array. The changeSchool function adds or removes the school._id from the users.schools array.
The changeSchool function:
const changeSchool = (schoolId) => {
let checked = checkSchool(schoolId);
if (!checked) {
// add schoolId to user.schools
} else {
// remove schoolId from user.schools
}
}
This drastically does not work. It appears that no matter what I use to mutate the state, the checkboxes never update, nor does the user.schools array mutate.
What is the proper way to structure such a design goal?
Assuming that you use UI Kitten, I can see that you got the checked prop value wrong for the CheckBox component.
UI Kitten CheckBox reference
The checked prop needs to be a boolean not a Callable as you have it there
I would try to change the code like this:
const renderAccessory = (school) => {
const isChecked = checkSchool(school._id);
return <CheckBox checked={isChecked} onChange={()=>changeSchool(school._id)} />
}
Let me know if that helped.
While trying various solutions i can conclude few things here:
With the solution given by #Cornel Raiu the checked and unchecked flags are getting correctly calculated however, the display was not correct with the state of checked/unchecked
I replaced Checkbox with Toggle, just to be sure that it works with iOS too
PROBLEM that i faced still is that, even the State of item getting toggled is correctly populating it was getting reset
The outside container of Toggles is List and ListItem,
OBSERVATION is that the Press event on List was actually getting the Checkbox/Toggle into correct Display State...
SOLUTION:
After longer time of research and experiments I got my thing working with following approach -
I maintained separate collection of Checked Items
There is already a state of Collection of master items, as input to List
Every time the Checkbox/Toggle is clicked, the master list of Data is cloned and copied back to its state
This was triggering the slight re-render of component and thing is working as expected.
const [cashTransactions, setCashTransactions] = useState([]); // master data
const [selectedTransactions, setSelectedTransactions] = useState([]); // selected data
const renderItem = ({ item, index }) => (
<ListItem
title={'('+item.id + ') ' + item.firstName + ' ' + item.lastName}
description={Moment(item.createdOn).format('yyyy-MM-DD hh:mm:ss')}
accessoryLeft={selectedTransactions.includes(item.id) ? RadioOnIcon : RadioOffIcon}
accessoryRight={() => checkBoxSpace(item)}
/>
);
const checkBoxSpace = (item) => {
let itemChecked = selectedTransactions.includes(item.id);
return (
<View style={styles.actionContainer}>
<Button size='tiny' status='basic' accessoryLeft={rupeeSymbol}>{item.amount}</Button>
<Toggle checked={itemChecked} status='primary' size='small' onChange={checked => checkboxChecked(item, checked)}></Toggle>
</View>
)
}
const checkboxChecked = (item, checked) => {
console.log('Item -' + item.id + ' ' + checked);
if (checked) {
if (!selectedTransactions.includes(item.id)) {
selectedTransactions.push(item.id);
}
} else {
if (selectedTransactions.includes(item.id)) {
selectedTransactions.pop(item.id);
}
}
console.log('selectedTransactions ' + JSON.stringify(selectedTransactions));
// This is the thing i applied to get it done.
const cloned = [...cashTransactions];
setCashTransactions(cloned);
}
// View
<List
style={styles.container}
data={cashTransactions}
renderItem={renderItem}
/>

React-native - How to create a scrollable flatList in which the chosen item is the one at the middle?

I would like to create a scrollable FlatList to select only one item among a list. After the user scroll the list, the selected item will be the one in the colored rectangle (which have a fixed position) as you can see here :
Actually I'm only able to render a basic FlatList even after some researches.
Do you know how I should do that ?
I found the solution (but it's not a FlatList) !
To do that I use :
https://github.com/veizz/react-native-picker-scrollview.
To define the background of the current selected items I added a new props highLightBackgroundColor in the ScrollPicker Class in the index file of react-native-picker-scrollview :
render(){
...
let highLightBackgroundColor = this.props.highLightBackgroundColor || '#FFFFFF';
...
let highlightStyle = {
...
backgroundColor: highLightBackgroundColor,
};
...
How to use it :
<ScrollPicker
ref={sp => {
this.sp = sp;
}}
dataSource={['a', 'b', 'c', 'd', 'e']}
selectedIndex={0}
itemHeight={50}
wrapperHeight={250}
highLightBackgroundColor={'lightgreen'}
renderItem={(data, index, isSelected) => {
return (
<View>
<Text>{data}</Text>
</View>
);
}}
onValueChange={(data, selectedIndex) => {
//
}}
/>
How it looks without others customizations:
You can implement the same setup with the very popular react-native-snap-carousel package, using the vertical prop. No need to use a smaller, poorly documented/unmaintained package for this.