I'm new to react-native and I have an input where I can choose multiple days of a week, if I click on a day which is alreadyAdded the day is removed from selectedDays array, however when I click on a day I get the following error:
TypeError: selectedDays.includes is not a function. (In selectedDays.includes(index+1) selectedDays.includes is undefined
My code:
const [ dayOptions, setDayOptions ] = useState([ 'Lunes','Martes','Miercoles','Jueves','Viernes','Sabado','Domingo']);
const [ selectedDays, setSelectedDays ] = useState([1]);
const clickedDay = (id)=>
{
const index = selectedDays.indexOf(id);
if (index > -1)
{
setSelectedDays(selectedDays.splice(index, 1));
}
else
{
setSelectedDays(selectedDays.push(id));
}
}
<View style={{ width:'100%',height:45,flexDirection:'row',alignItems:'center',flexWrap:'nowrap', justifyContent:'space-between' }}>
{dayOptions.map((option,index) => {return (
<TouchableOpacity key={option.title} onPress = {() => {clickedDayOptions(index+1) }} style={{ width:'13.2%', height:'100%', flexDirection:'row', alignItems:'center',justifyContent:'center', borderRadius:5,borderWidth:1, borderColor:'rgba(0,0,0,0.2)', ...selectedDays.includes(index+1) ? { backgroundColor: 'rgb(255,52,89)' } : { backgroundColor:'white' }, }}>
<Text style={{ fontWeight:'bold',fontSize:14, ...selectedDays.includes(index+1) ? { color: 'white' } : { color:'rgb(68,68,68)' }, }}>{ option }</Text>
</TouchableOpacity>)
})}
</View>
I tried changing includes for indexOf() > 1 and I get the same error, I usually get this when it's not an array but I defined selectedDays as an array in hooks...
setSelectedDays(selectedDays.push(id)); Generally Array.prototype.push will return the length of the array after push operation is done, so here we are making Array to number so that you are getting indexOf, includes is not a function error because it's a number type
Try like below
const clickedDay = (id) => {
const index = selectedDays.indexOf(id);
if (index > -1) {
selectedDays.splice(index, 1)
setSelectedDays([...selectedDays]);
} else {
setSelectedDays([...selectedDays, id]);
}
};
const clickedDay = (id) => {
const index = selectedDays.indexOf(id);
if (index > -1) {
const days = selectedDays.filter((idx) => idx !== id);
setSelectedDays(days);
} else {
setSelectedDays([...selectedDays, id]);
}
};
Related
I am trying to create a custom range slider, and to get the value of the translationX and pass it as a prop and/or into my redux, however this has seemed impossible, as it does not update, even when I use useState.
Has any one experienced this issue in the past.
Below is my current code
const CustomSlider = ({ onPressOut, testData }) => {
const translateX = useSharedValue(SCREEN_WIDTH / 2);
const context = useSharedValue({ x: SCREEN_WIDTH / 2 });
console.log(testData);
const handleGesture = Gesture.Pan()
.onStart(() => {
context.value = { x: translateX.value };
})
.onUpdate(event => {
translateX.value = event.translationX + context.value.x;
if (translateX.value < SCREEN_WIDTH / 2) {
translateX.value = Math.max(translateX.value, MIN_TRANSLATE_X);
} else {
translateX.value = Math.min(translateX.value, MAX_TRANSLATE_X);
}
testData(translateX.value);
});
const rBottomSheetStyle = useAnimatedStyle(() => {
return {
left: translateX.value,
};
});
return (
<View style={styles.container}>
<View style={styles.negativeLineStyle} />
<View style={styles.positiveLineStyle} />
<GestureDetector gesture={handleGesture}>
<AnimatedTouchableOpacity
activeOpacity={1}
onPressOut={onPressOut}
style={[rBottomSheetStyle, styles.rangeSliderContainer]}>
<LinearGradient
colors={[SECONDARY, PRIMARY]}
start={[1, 0.9]}
end={[1, 0]}
style={styles.rangeSliderStyle}
/>
</AnimatedTouchableOpacity>
</GestureDetector>
</View>
);
};
After to read this page:
https://typeofnan.dev/fix-the-react-hook-is-called-conditionally-error-in-react/
I try to find the error but i keep getting the error:
React Hook "useState" is called conditionally
here the code:
export const PageSettings = (props) => {
const {
table,
listviewFields,
t,
tableCrud,
updatePageSettingsFields,
templates,
visibleFields,
visibleFieldsToggle,
} = props;
let aFields = [];
let aFieldsForSortable = [];
for (const [keyName, fieldSet] of Object.entries(listviewFields)) {
const listViewField = table.listview.fields[keyName];
let fieldSource;
if (listViewField.virtual) {
// virtual don't throw error, must have always label
fieldSource = listViewField;
} else {
fieldSource = table.fields[keyName];
}
const field = table.fields[keyName];
const { aColHeaderToAdd, titleFieldFull, printCell } = getHeaderToAdd(
listViewField,
keyName,
fieldSource,
table,
props,
t,
templates,
tableCrud,
fieldSet,
listviewFields
);
if (printCell) {
aFieldsForSortable.push({ id: keyName, name: titleFieldFull });
aFields.push(
<div
key={keyName}
style={{
fontSize: "12px",
minWidth: "90px",
maxWidtht: "150px",
bgColor:
listviewFields && listviewFields[keyName].invisible === true
? "black"
: "green",
}}
>
<input
type="checkbox"
name={keyName}
style={{}}
checked={
!listviewFields[keyName].invisible ||
listviewFields[keyName].invisible === false
? true
: false
}
onChange={updatePageSettingsFields}
/>
<label htmlFor={keyName}>{titleFieldFull}</label>
</div>
);
}
}
const [stateList, setStateList] = useState([aFieldsForSortable]);
return (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
color: "#FFFFFF",
paddingTop: "20px",
width: "100%",
backgroundColor: visibleFields ? "#555555" : null,
}}
>
<a onClick={visibleFieldsToggle}>
<ShowIcon icon="eyecheck" color="#5f97c1" />
</a>
<ReactSortable list={stateList} setList={setStateList}>
{state.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</ReactSortable>
{visibleFields && aFields}
</div>
);
};
const getHeaderToAdd = (
listViewField,
keyName,
fieldSource,
table,
props,
t,
templates,
tableCrud,
fieldSet,
listviewFields
) => {
let colsHeader = [];
let printCell = true;
// Logic Twin TT103
let titleField;
let titleFieldFull;
// fieldSource can be not exist, by example , virtual field: customers.listview.addresses2
if (
fieldSource &&
fieldSource.fieldDisplayedOptions &&
fieldSource.fieldDisplayedOptions.separatorsubfield === "col"
) {
/*
not call main field, but the subfields to get label
*/
for (const [nameSubField, objField] of Object.entries(
fieldSource.fieldDisplayedOptions.subfields
)) {
titleField = resolveListLabel(table.related[keyName].table, nameSubField); // look for related table
titleFieldFull =
titleFieldFull + (titleFieldFull ? " " : "") + titleField;
colsHeader.push(
<div className="cell" key={nameSubField + keyName}>
{t(titleField)}
</div>
);
}
} else {
if (listViewField.module) {
if (
!resolvePathObj(
props,
"myState.app.appSettings.modules." + listViewField.module,
true
)
) {
//if (keyName === 'dateaccounting') console.log('module not compat', props);
printCell = false;
}
}
if (listViewField.templates) {
if (
!intersection(
listViewField.templates,
templates,
props.tableCrud,
keyName
)
) {
//if (keyName === 'dateaccounting') console.log('no template');
printCell = false;
}
}
}
/*if (keyName === 'dateaccounting') {
console.log('module, propstemplates, templates', listViewField.module, templates, listViewField.templates);
}*/
if (printCell) {
titleFieldFull = resolveListLabel(tableCrud, keyName);
// try find short word, if not exist then get normal word
let title = t(titleFieldFull + "_short");
if (title === titleFieldFull + "_short") title = t(titleFieldFull);
titleFieldFull = title;
colsHeader.push(
<div className="cell" key={keyName}>
{titleFieldFull}
</div>
);
}
return { titleFieldFull, printCell, colsHeader };
};
thanks
You're passing the argument aFieldsForSortable which is initialized as [] and then conditionally (inside the if statement), you're writing information for that value.
I'd suggest starting the use state as empty and in case you're changing it, call the setStateList method. For example,
const [stateList, setStateList] = useState([]);
....
if (printCell) {
const aFieldsForSortable = stateList;
aFieldsForSortable.push({ id: keyName, name: titleFieldFull });
setStateList(aFieldsForSortable);
You are breaking the Rules of Hooks.
Rules of Hooks say:
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions
The useState() call should be at the top of your function. If your initial state is based on a complex calculation, then you should do that calculation inside useEffect() (setting the value using setStateList()), otherwise the complex calculation will be re-run every time your Component is re-rendered.
I have an Ul component which contains an array of objects with different values in it. Each item called TestCase in this list has a button which makes a restcall and updates its object. However not all TestItems need to be updated. Only those whose button are clicked. The state of this array is stored in a parent continer component called TestCaseContainer. My button will updates the state accordingly to the effected TestItem in the array, however. This causes the whole list to rerender. How can I only have the changed TestItems rendered, instead of having the entire ul rendered every time an element is updated. I read about using useMemo so the component can memoize the passed down props, however I don't know how to implement this properly.
How can I stop all the rerenders?
Regression.js - Holds all the state
const Testing = forwardRef((props,ref) => {
const templateTestItem = {id:0,formData:{date:'',env:'',assetClass:'',metric:'',nodeLevel:'',nodeName:'',testName:'',dataType:'',tradeId:''},results:[],isLoading:false}
const testCaseRef = useRef()
const [isRun, setIsRun] = useState(false)
const [testItems, setTestItems] = useState([ templateTestItem])
const [stats,setStats] = useState(null)
const addTestItem = () => {
const newIndex = testItems.length
// console.log(newIndex)
const templateTestItem = {id:newIndex,formData:{date:'',env:'',assetClass:'',metric:'',nodeLevel:'',nodeName:'',testName:'',dataType:'',tradeId:''},results:[],isLoading:false}
setTestItems([...testItems, templateTestItem])
}
const addUploadCases = (cases) => {
setTestItems([])
const UploadedItems = cases.map((item,index)=>{
return{
id:index,
formData:{
date:item['date'],
env:item['env'],
assetClass:item['asset_class'],
metric:item['metric'],
nodeLevel:item['node_level'],
nodeName:item['node_name'],
testName:item['test_name'],
dataType:item['dataType'],
tradeId:item['tradeId']
},
results:[]
}
})
setTestItems(UploadedItems)
}
const runAllTests = () => {
testCaseRef.current.runAll()
}
const clearTestCases = () => {
// console.log('Clear Test cases')
setTestItems([])
if (testItems.length == 0) {
setTestItems([templateTestItem])
}
}
const extractAllResults =()=>{
testCaseRef.current.extractAllResults()
}
const updateTestResults = useCallback( (result, index) => {
console.log('Index:', index)
setTestItems(prevObjs=>(prevObjs.map((item)=>{
let updatedItem = { ...item, results: result }
if(item.id==index) return updatedItem
return item
})))
},[])
return (
<div style={{ 'backgroundColor': '#1b2829', 'display': 'flex', }} className={styles.dashboard}>
<Grid>
<Row stretched style={{}} className={styles.buttonConsole}>
{<ButtonConsole addTest={addTestItem} addUploadCases={addUploadCases} runAllTests={runAllTests} clearTestCases={clearTestCases} extractAllResults={extractAllResults} />}
</Row>
<Row centered>
<TestRunStats stats={stats}/>
</Row>
<Row style={{ 'display': 'flex', 'flex-direction': 'column' }} ><TestCaseContainer countTestRunStats={countTestRunStats} updateTestResults={updateTestResults} isRun={isRun} ref={testCaseRef} testItems={testItems} /> </Row>
{/*
<Row></Row>
<Row></Row> */}
</Grid>
</div>
);
})
TestContainer.js
const TestCaseContainer = forwardRef((props, ref) => {
const testCaseRef = useRef([])
useImperativeHandle(ref, () => ({
extractAllResults: async () => {
const data = {
data:[],
summary:[]
}
testCaseRef.current.forEach(async (item, index) => {
try {
const workbook = item.extractAllResults()
const summary = workbook['summary']
workbook['data'].forEach(testData => {
data['data'].push(testData)
})
data['summary'].push(summary)
} catch (err) {
console.log(err)
}
})
await axios.post('http://localhost:9999/api/downloadresults', data).then(res => {
console.log('res', res)
const byteCharacters = atob(res.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'application/vnd.ms-excel' });
saveAs(blob, 'TestResults.xlsx')
})
},
runAll: () => {
testCaseRef.current.forEach(async (item, index) => {
await item.runAll()
})
}
}));
const runTestCase = async (date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key) => {
let testKey = key
console.log('FEtCHING', testKey)
try {
const params = {
nodeName,
date,
env,
nodeLevel,
assetClass,
metric,
dataType,
tradeId,
testName
}
const endpoint ={
sensitivities:'sensitivities'
}
if (metric == 'DELTA_SENSITIVITIES') {
const result = await axios.get('example.net/api/sensitivities', { params, }).then(response => {
console.log('response.data', response.data)
return response.data
})
if (result.data == 'none') {
toast.error(`${date}-${metric}-${nodeName} failed queried! No valutations for trades`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
} else if (result.data != 'none') {
// setTestResult(result)
props.updateTestResults(result, testKey)
// updateTestResults(false,testKey,'isLoading')
toast.success(`${date}-${metric}-${nodeName} Successfully queried!`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
// setTestResult(result.data)
} else {
await axios.get(`http://localhost:9999/api/metric/${metric}`, { params, }).then(response => {
if (response.data != 'none') {
props.updateTestResults(response.data, testKey)
toast.success(`${date}-${metric}-${nodeName} Successfully queried!`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
} else {
toast.error(`${date}-${metric}-${nodeName} failed queried! No valutations for trades`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
})
}
} catch (error) {
toast.error(`${date}-${metric}-${nodeName} failed queried! -${error
}`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
}
return (
<Segment style={{ 'display': 'flex', 'width': 'auto', 'height': '100vh' }} className={styles.testCaseContainer}>
<div style={{ 'display': 'flex', }}>
</div>
<ul style={{overflowY:'auto',height:'100%'}} className='testItemContainer'>
{
// memoTestTwo
// testList
props.testItems.map((item, index) => {
let testName
if (item['formData']['testName'] == '') {
testName = `testRun-${index}`
} else {
testName = item['formData']['testName']
}
return <TestCase testResult={item['results']} runTestCase={runTestCase} isRun={props.isRun} ref={el => (testCaseRef.current[index] = el)} testKey={index} key={index} date={item['formData']['date']} env={item['formData']['env']} assetClass={item['formData']['assetClass']} metric={item['formData']['metric']} nodeLevel={item['formData']['nodeLevel']} nodeName={item['formData']['nodeName']} testName={testName} dataType={item['formData']['dataType']} tradeId={item['formData']['tradeId']} hierarchy={hierarchy} />
})
}
</ul>
</Segment>
)
})
TestCase.js - the individual item rendered from mapping!
const TestCase = forwardRef((props, ref) => {
const [isLoading, setIsLoading] = useState(false)
const inputRefs = useRef()
const outputRefs = useRef()
useImperativeHandle(ref, () => ({
extractAllResults: () => {
return outputRefs.current.extractAllResults();
},
runAll: () => {
inputRefs.current.runAll()
},
}));
const runSingleTestCase = async (date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key) => {
setIsLoading(true)
await props.runTestCase(date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key)
setIsLoading(false)
}
const convertDate = (date) => {
if (date) {
const newDate = date.split('/')[2] + '-' + date.split('/')[0] + '-' + date.split('/')[1]
return newDate
} else {
return date
}
}
return (
<Segment color='green' style={{ 'display': 'flex', 'flexDirection': 'column', }}>
<div style={{ 'display': 'flex', 'justify-content': 'space-between' }}>
<div style={{ 'display': 'flex', 'height': '30px' }}>
<Button
// onClick={props.deleteSingleTest(props.testKey)}
icon="close"
inverted
size="tiny"
color='red'
></Button>
</div>
<RegressionInput runSingleTestCase={runSingleTestCase} isRun={props.isRun} testKey={props.testKey} ref={inputRefs} nodeNames={props.hierarchy} runTestCase={props.runTestCase} date={convertDate(props.date)} testName={props.testName} env={props.env} assetClass={props.assetClass} metric={props.metric} nodeLevel={props.nodeLevel} nodeName={props.nodeName} dataType={props.dataType} tradeId={props.tradeId} />
<TestCheck pass={props.testResult ? props.testResult['CHECK'] : null} />
</div>
{
isLoading ? (<Loading type={'circle'} style={{ 'display': 'flex', 'flexDirecton': 'column', 'justify-content': 'center', 'align-items': 'center', 'marginTop': '50' }} inline />) : (
<RegressionOutput ref={outputRefs} testName={props.testName} testResult={props.testResult} />
)
}
</Segment>
)
})
This article might help you understand React rendering behavior better:
Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior
React's default behavior is that when a parent component renders, React will recursively render all child components inside of it!
To change that behavior you can wrap some of your components in React.memo(). So React will do a shallow compare on the props object and only re-render that if one of the top level properties of the props object has changed.
That's not always possible or recommended, especially if you are using props.children.
const TestItem = React.memo(({id,value}) => {
console.log(`Rendering TestItem ${id}...`);
return(
<div>TestItem {id}. Value: {value}</div>
);
});
const App = () => {
console.log("Rendering App...");
const [items,setItems] = React.useState([
{ id: 1, value: "INITIAL VALUE" },
{ id: 2, value: "INITIAL VALUE" },
{ id: 3, value: "INITIAL VALUE" },
]);
const testItems = items.map((item,index) =>
<TestItem key={index} id={item.id} value={item.value}/>
);
const updateTest = (index) => {
console.clear();
setItems((prevState) => {
const newArray = Array.from(prevState);
newArray[index].value = "NEW VALUE";
return newArray
});
};
return(
<React.Fragment>
<div>App</div>
<button onClick={()=>{updateTest(0)}}>Update Test 1</button>
<button onClick={()=>{updateTest(1)}}>Update Test 2</button>
<button onClick={()=>{updateTest(2)}}>Update Test 3</button>
<div>
{testItems}
</div>
</React.Fragment>
);
};
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
Without the React.memo() call. Every re-render of the App component would trigger a re-render in all of the TestItem component that it renders.
I have a simple Notes React Native app and I am able to add and get data to it, but I am not sure how to remove/update data. The main problem is in getting the part where I tell firebase which data to remove. How can I pass a firebase key to a 'delete' function that takes the key as parameter and remove it from firebase.
I'm an absolute beginner at React Native, my code is the following:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
all_notitas: [],
notita_text: ''
};
};
componentWillMount() {
const notitasRef = firebase.database().ref('notitas');
this.listenForNotitas(notitasRef);
};
listenForNotitas = (notitasRef) => {
notitasRef.on('value', (dataSnapshot) => {
var aux = [];
dataSnapshot.forEach((child) => {
aux.push({
date: child.val().date,
notita: child.val().notita
});
});
this.setState({all_notitas: aux});
});
}; // listenForNotitas
render() {
let show_notitas = this.state.all_notitas.map((val, key) => {
return (
<Notita
key={key}
keyval={key}
val={val}
eventDeleteNotita={()=>this.deleteNotita(key)}> // I THINK THIS IS THE PROBLEM
</Notita>
);
});
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>NOTITAS</Text>
</View>
<ScrollView style={styles.scrollContainer}>
{show_notitas}
</ScrollView>
<View style={styles.footer}>
<TouchableOpacity
style={styles.addButton}
onPress={this.addNotita.bind(this)}>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
<TextInput
style={styles.textInput}
placeholder='>>> Escribir notita'
placeholderTextColor='white'
underlineColorAndroid='transparent'
onChangeText={(notita_text) => (this.setState({notita_text}))}
value={this.state.notita_text}>
</TextInput>
</View>
</View>
);
}
addNotita() {
if (this.state.notita_text) {
var d = new Date();
dataForPush = {
'date': d.getDate() + '-' + d.getMonth() + '-' + d.getFullYear(),
'notita': this.state.notita_text
};
firebase.database().ref('notitas').push(dataForPush);
this.state.all_notitas.push(dataForPush);
this.setState(
{
all_notitas: this.state.all_notitas,
notita_text: '', // Limpiar input
}
)
} // end if
} // addNotita
When I do 'console.log(key)', it returns an int like 0, 1, 2, etc. It should return a firebase key like '-LRtghw8CjMsftSAXMUg' for example. I don't know what I am doing wrong and how to fix it.
deleteNotita(key) {
firebase.database().ref('notitas').child('' + key).remove()
/*
let updates = {};
console.log(key);
console.log(updates['/notitas/' + key]);
updates['/notitas/' + key] = null;
firebase.database().ref().update(updates); */
this.state.all_notitas.splice(key, 1);
this.setState({all_notitas: this.state.all_notitas});
} // deleteNotita
}
You're dropping the key from Firebase when you're adding the notes to aux. The solution is to also keep the key from Firebase in there:
notitasRef.on('value', (dataSnapshot) => {
var aux = [];
dataSnapshot.forEach((child) => {
aux.push({
date: child.val().date,
notita: child.val().notita,
id: child.key
});
});
this.setState({all_notitas: aux});
}
And then pass that value to deleteNotita with:
this.deleteNotita(val.id)
I have a SectionList with a RN elements ListItem, I have hidden the chevron and set the switch state to a state variable. the onSwitch method is not working, or at least i cant get it to work.
I have tried a few different ways of implementing it to no avail.
<SectionList
sections={[
{title: 'Company Focus', data: this.state.focusNodes},
]}
renderSectionHeader={({section}) => <Text style=
{styles.sectionHeader}>{section.title}</Text>}
renderItem={({item}) => <ListItem style={styles.subTitle} key=
{item.id} title={item.name}
switchButton hideChevron switched=
{this.state.isNodeSelected} switchOnTintColor={'#00BCD4'}
onSwitch={(value) => {
this._handleSwitch(value);
}}/>} />
_handleSwitch = (item) => {
this.state.isNodeSelected = true;
console.log(item);
console.log(this.state.isNodeSelected);
}
The other way:
onSwitch={(value) => {
this.setState(previousState => {
return{previousState, isNodeSelected: value}})
}}/>} />
The switch in the first example just moves then moves back. In the second example it moves all the switches in the list to on or off regardless of which one i switch on/off
EDIT - THE WHOLE COMPONENT :)
'use strict';
import React from 'react';
import { StyleSheet,
Text,
View,
NavigatorIOS,
ScrollView,
Button,
PickerIOS,
Dimensions,
SectionList,
TouchableHighlight,
} from 'react-native';
import {Header, ListItem, List} from 'react-native-elements';
import StepIndicator from 'react-native-step-indicator';
export default class FocusPage extends React.Component {
constructor(props){
super(props);
this.state ={
data : [],
roleNodes: [],
skillNodes: [],
focusNodes: [],
// roleSelection: '',
// childSelection: '',
isVisible: false,
currentPosition: 0,
selectedNodes: [],
isFocusNodeSelected: false
}
}
_createFocusNodesArray = (array) => {
var nodeArray =[];
for(var i = 0; i < array.length; i++){
if(array[i].orderNumber === 2)
nodeArray.push(array[i]);
}
return nodeArray;
}
_getFocusData = () => {
fetch('http://10.0.0.58:8082/api/getfocusnodes', {
method: 'GET'
})
.then((response) => response.json())
.then((responseJson) => {
// this.setState({
// data: responseJson
// })
var simpleArray =
this._simplifyJsonArray(responseJson);
// console.log(simpleArray);
this.setState({
focusNodes:
this._createFocusNodesArray(simpleArray),
})
})
.catch((error) => {
console.log(error);
// this.setState({
// isLoading: false,
// message: 'Woops! ' + error
// })
})
}
//'http://10.0.0.58:8082/api/gettreenodes'
//'http://192.168.6.217:8082/api/gettreenodes'
componentDidMount = () => {
this._getFocusData();
}
_simplifyJsonArray = (array) => {
var tempArray = [];
for(var i = 0; i < array.length; i++)
{
var tempNode = {
id: array[i].id,
name: array[i].name,
orderNumber: array[i].orderNumber,
isSelected: false
}
tempArray.push(tempNode);
}
return tempArray;
}
getSelection = (selectedItem) => {
console.log(selectedItem);
// selectedItem.isSelected = true;
// this.state.isNodeSelected = true;
for (let index = 0; index < this.state.focusNodes.length;
index++) {
const element = this.state.focusNodes[index];
if(element.name === selectedItem){
console.log(element.name);
this.state.isFocusNodeSelected = true;
}
}
if (selectedItem.isSelected) {
this.state.isFocusNodeSelected = true;
}
// console.log(this.state.isNodeSelected);
// for (let i = 0; i < this.state.focusNodes.length; i++) {
// const element = this.state.focusNodes[i];
// if (element.name === selectedItem) {
// element.isSelected = true;
// this.state.isNodeSelected = element.isSelected;
// this.state.selectedNodes.push(element);
// console.log(this.state.isNodeSelected);
// }
// else{
// this.state.isNodeSelected = false;
// }
// }
// console.log('The selected item: ' + selectedItem);
// console.log('The selected Array: ' + this.state.selectedNodes);
}
_handleSwitch = (item) => {
this.state.isFocusNodeSelected = true;
console.log(item);
console.log(this.state.isFocusNodeSelected);
}
render() {
return (
<View style={styles.container}>
<SectionList
sections={[
{title: 'Company Focus', data: this.state.focusNodes},
]}
renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
renderItem={({item}) => <ListItem style={styles.subTitle} key={item.id} title={item.name}
switchButton hideChevron switched={item.isSelected} switchOnTintColor={'#00BCD4'}
onSwitch={(value) => {
this.setState(previousState => {
return{previousState, isFocusNodeSelected: value}
})
}}/>} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
pageTitle:{
fontSize: 27,
textAlign: 'center',
//margin: 20
},
sectionHeader:{
fontSize: 20,
backgroundColor: '#673AB7',
height: 40,
textAlign: 'center',
padding: 10,
color: 'white'
},
subTitle: {
fontSize: 16,
// textAlign: 'center',
// marginTop: 20,
backgroundColor: '#00BCD4'
},
popButton: {
borderRadius: 10,
padding: 5,
// marginLeft: 5,
marginRight: 5,
backgroundColor: '#00BCD4',
borderColor: 'black',
borderWidth: 1,
},
subtitleView: {
flexDirection: 'row',
paddingLeft: 10,
paddingTop: 5,
textAlign: 'right'
},
});
In your first example, the reason why your switches get flipped back is because you are always setting your switches to true, so they can never get set to false:
this.state.isNodeSelected = true;
That's not the only problem though. As you see in your second example, you fix this by setting the value to what get's passed into onSwitch as value. That's good. Now the issue of having all switches flip is because you are using the same state value for all switches.
switched={this.state.isNodeSelected}
So what is happening is that when you update isNodeSelected, you are updating it for every switch.
To fix this, you need to hold the switched value for each ListItem somewhere; probably the most straightforward would be in what you pass to sections.
sections={[
{title: 'Company Focus', data: this.state.focusNodes},
]}
The data you pass into your sections prop should be kept in your state so you can update the specific item that whose switch is flipped. However, without seeing your state code, it's hard to tell what you're doing and how exactly to fix it. The above explanation should be enough to get you to a solution though. Just remember that renderItem also gets an index argument which are shown in the simple examples from the docs and explained further in the prop docs.
Edit: With the edited in info and changes, we now have a renderItem where each ListItem has its own switched value stored in item.isSelected. So given that, our goal is to have a onSwitch that updates just that value for just that item. So what do we update?
Well, the SectionList's sections prop is getting that data from this.state.focusNodes (what data is set to). So updating the correct value in focusNodes is what needs to happen. As I alluded to above, one way to do this is to leverage renderItem's index argument:
renderItem={({item, index}) =>
<ListItem
style={styles.subTitle}
key={item.id}
title={item.name}
switchButton
hideChevron
switched={item.isSelected}
switchOnTintColor={'#00BCD4'}
onSwitch={(value) => {
let focusNodes = [...this.state.focusNodes];
focusNodes[index].isSelected = value;
this.setState({ focusNodes, isFocusNodeSelected: value });
}}
/>
}
Notes:
I used index to figure out which of the focusNodes needs to get updated. Since you are using SectionList, read the docs to understand how this index value gets determined. Be careful and don't make assumptions once you start using multiple sections. I say this because...
I noticed that data in your state is unused. If you eventually refactor and change your sections prop to use this instead or you move focusNodes into that, you'll have to refactor what is being updated. Take care and understand how your data is structured and not make a bad assumption about index.
In case the missing previousState threw you off, I used a shorthand in setState to make it cleaner.
I'm making the assumption that the value being passed to onSwitch is the correct boolean value that isSelected needs to be updated to. I don't have a test project setup to run this to confirm.