React Native: Checkbox List Structure - react-native

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}
/>

Related

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.

Creating a checkbox group with 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>
);
}
}

React Native. Filter FlatList - error "index=10 count 0"

My task is to filter some array and set it to FlatList.
My filter function is:
updateInvoiceList = (text) => {
let invoiceList = [...this.state.baseInvoiceList];
invoiceList = invoiceList.filter(el => {
return el.name.toLowerCase().includes(text.toLowerCase())
});
this.setState({invoiceList})
}
After filtering, I provide state.invoiceList to FlatList and everything works correctly. But, when I set some symbol which does not exist in my array, for example "!", the function clears the array and it still behaves correctly. When I remove the symbol "!", I get an error screen with:
index=10 count=0
addInArray
ViewGroup.java:5235
addViewInner
ViewGroup.java:5128
addView
ViewGroup.java:4935
addView
ReactViewGroup.java:452
addView
ViewGroup.java:4875
addView
ReactViewManager.java:269
addView
ReactViewManager.java:36
manageChildren
NativeViewHierarchyManager.java:346
execute
UIViewOperationQueue.java:227
run
UIViewOperationQueue.java:917
flushPendingBatches
UIViewOperationQueue.java:1025
access$2600
UIViewOperationQueue.java:46
doFrameGuarded
UIViewOperationQueue.java:1085
doFrame
GuardedFrameCallback.java:29
doFrame
ReactChoreographer.java:166
doFrame
ChoreographerCompat.java:84
run
Choreographer.java:964
doCallbacks
Choreographer.java:790
doFrame
Choreographer.java:721
run
Choreographer.java:951
handleCallback
Handler.java:883
dispatchMessage
Handler.java:100
loop
Looper.java:214
main
ActivityThread.java:7356
invoke
Method.java
run
RuntimeInit.java:492
main
ZygoteInit.java:930
What did I do wrong?
I had the exact same issue with my code, inside a Flatlist and only showing up on Android. I've managed to solve it as follows:
My FlatList had
stickyHeaderIndices={this.state.items[1]}
but apparently the List was loading that stickyHeader ahead of its initialization. From here, the solution consists simply in handling the case in which that item is not initialized yet.
stickyHeaderIndices={this.state.items.length > 0 ? [1] : [0]}
Hope this helps! The solution is pretty straightforward. Debugging it can be a real pain, tho!
On Android, you must not initialise your FlatList stickyHeaderIndices with empty array.
Instead, when your state is not loaded yet, provide FlatList data with some initial data and provide stickyHeaderIndices with [0]
Example code as below
let [data, stickyIndices] = someAPIToGetYourData()
...
...
...
// if your data is not finished loading yet
if (data.length === 0) {
// create not-empty array
data = getInitialNotEmptyData()
// add not-empty index
headerIndexList = [0]
}
return (
<SafeAreaView>
<FlatList
data={data}
stickyHeaderIndices={headerIndexList /* This must not be empty */ }
renderItem={item => renderYourItem(item)}
/>
</SafeAreaView>
)
Hope this help!
I needed kill the FlatList to fix this error, like this:
<View>
{ this.state.data.length > 0 &&
<FlatList
showsVerticalScrollIndicator={false}
refreshing={false}
onRefresh={() => this.onRefresh()}
data={this.state.data}
extraData={this.state}>}
</View>
Hope this helps!
LayoutAnimation causes a similar issue, not only with Flatlist but any kind of lists, check if you are using it in your app and if you have UIManager.setLayoutAnimationEnabledExperimental(true) on your top component, removing it might fix your issue, but will disable the layoutAnimations.
You might find more information here and here
Please improve your function.
updateInvoiceList = (text) => {
let invoiceList = [];
const { baseInvoiceList } = this.state;
if(text){
invoiceList = baseInvoiceList.filter(item => {
return item.name.toLowerCase().includes(text.toLowerCase());
});
}
this.setState({invoiceList});
}

Settings different Values of element on a state

So in my react native, I have a spinner which I am using to enter numbers. It has two buttons which increases or decreases a value. But the problem is that I have to set the value to a state and I have multiple elements. So if I change the value of one element, everything else changes too.
Here is the Package
And here is a sample code I am working with:
this.state = {
qty: null,
}
<InputSpinner
max={50}
min={1}
step={1}
width={100}
height={50}
colorMax={"#2a292d"}
colorMin={"#2a292d"}
buttonFontSize={13}
value={this.state.qty}
onChange={(num) => {
this.setState({qty: num});
}}/>
So on change I am settings the qty state. But I have multiple spinners and changing one changes everything because each uses the same state. What would be the better solution for this? Should I use an array to store each item qty?
For me the better solution is assign at each Spinner an ID and then create an object with key = spinnerID and value = num
this.state = {
qty: {},
}
<InputSpinner
max={50}
min={1}
step={1}
width={100}
height={50}
colorMax={"#2a292d"}
colorMin={"#2a292d"}
buttonFontSize={13}
value={this.state.qty['1'] || 1}
onChange={(num) => {
let qty = Object.assign({}, this.state.qty);
qty['1'] = num;
this.setState({qty});
}}/>
yes, obviously you need to maintain multiple states for each spinner, never use one state for that. I would recommend to use an array like
`
this.state = {
spinnerValues:[]
}
`
and onChange of that input spinner you can do somewhat like
`
onChange={(num) => {
let currentState = this.state.spinnerValues;
currentState[i] = num; // here i is the index which you will provide for the spinner num
this.setState({spinnerValues: currentState});
`
and for value of each spinner
value = {this.state.spinnerValues[i]}

react-native update component, re-render view, on success automatically without state or props

I have this function
export function chooseContact(){
ContactsWrapper.getContact().then((contact) => { //opens stock phone address book
var phone = contact['phone']; // retrieves phone number from picked contact
global.phoneDemo = phone; //initializes to the global var in order to access from anywhere
}).catch((error) => { // these line standard
console.log("ERROR CODE: ", error.code);
console.log("ERROR MESSAGE: ", error.message);
});}
After successfully storing phone number from contacts in a global var I am using it in my render view method
const Home = ({
home
value: {client},
onClientChange,
onSubmit}) => (
<View style={styles.container}>
<InputGroup borderType="underline" style={styles.inputGroup}>
<Icon name="md-contact" style={styles.icon}/>
<Input value={global.phoneDemo?phoneDemo:client}
onChangeText={onClientChange}/>
<Icon name="ios-contacts" style={styles.addressBook} onPress= {chooseContact}/> //this acceesses above function and opens stock contacts on a phone
);
Everything is fine except one thing after storing the phone number in a global variable i am able to set it automatically on a input if i re-navigate to another component and return again, in that occasion only input fills automatically other ways no. How can i set it automatically after pick contact.