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.
Related
I am iterating over an array, and for each element, I pass it to a handleClick function. The question is, inside that handleClick function, how do I access the rest of the elements?
const listOfAnswers = questions[questionNumber].possibleAnswers.map((obj, i, arr) => {
return (
<Button
key={i}
style={
{
margin: '15px 0',
}
}
variant='contained'
onClick={e => handleClick(obj, e, arr)}
>
{obj.answer}
</Button>
)
})
const handleClick = async (obj, e, arr) => {
const { isCorrect, answer } = obj
if (isCorrect) {
setScore(score + 1)
e.target.style.backgroundColor = 'green'
await delay(100)
e.target.style.backgroundColor = ''
} else {
e.target.style.backgroundColor = 'red'
await delay(100)
e.target.style.backgroundColor = ''
}
nextQuestion()
}
What I am trying to do is: when a user clicks on the right answer, that button turns green. This is straightforward to implement. When a user clicks the wrong answer, it turns red. Also simple to implement. But what I want is: when a user clicks on the wrong answer, I want the right answer to turn green. For this I think I need to be able to access the rest of the elements, because in the handleClick function, you only have access to a single, individual element.
const [clicked, setClicked] = useState(-1);
const listOfAnswers = questions[questionNumber].possibleAnswers.map((obj, i, arr) => {
return (
<Button
key={i}
style={
{
margin: '15px 0',
color: clicked == -1
? "#ffffff"
: clicked == i && obj.isCorrect
? "#00ff00"
: cliked != i
? "#ffffff"
: "#ff0000"
}
}
variant='contained'
onClick={e => handleClick(i)}
>
{obj.answer}
</Button>
)
})
const handleClick = async (i) => {
setClicked(i)
nextQuestion()
}
const [clicked, setClicked] = useState(-1);
const listOfAnswers = questions[questionNumber].possibleAnswers.map((obj, i, arr) => {
return (
<Button
key={i}
style={
{
margin: '15px 0',
color: clicked == -1
? "#ffffff"
: clicked == i && obj.isCorrect
? "#00ff00"
: cliked != i
? "#ffffff"
: "#ff0000"
}
}
variant='contained'
onClick={handleClick}
>
{obj.answer}
</Button>
)
})
const handleClick = (e) => {
setClicked(e.target.key)
}
There are various way to do it:
Add a answer to the state (initialized to undefined). Your handleClick will set this state to the answer the user selected. Then if answer is defined, pass a green backgroundColor in the style of the correct button (next to your margin). And on the button whose obj.answer === answer, if obj.isCorrect is false, set a red backgroundColor. (note you would need to reset answer state to undefined in your nextQuestion)
Add a ref to the correct answer Button (https://reactjs.org/docs/refs-and-the-dom.html), and on handleClick, you can set ref.current.style.background.
UPDATE with example of ref
The following assumes that there is a unique correct answer (link to codesandbox: https://codesandbox.io/s/hungry-swirles-7q6wz2?file=/src/App.js:0-2575):
import "./styles.css";
import { useRef, useState } from "react";
import { Button } from "#mui/material";
export default function App() {
return (
<div className="App">
<h1>Quiz time!</h1>
<Questions />
</div>
);
}
const Questions = () => {
const [score, setScore] = useState(0);
const [questionNumber, setQuestionNumber] = useState(0);
const correctAnswerRef = useRef(null);
// If all questions were answered we display the score
if (questionNumber >= questions.length) {
const reset = () => {
setScore(0);
setQuestionNumber(0);
};
return (
<div>
Score: {score} <Button onClick={reset}>Take the quiz again</Button>
</div>
);
}
const handleClick = async (obj, e) => {
const { isCorrect } = obj;
// always set current answer to green
if (correctAnswerRef.current) {
correctAnswerRef.current.style.backgroundColor = "green";
}
if (isCorrect) {
setScore(score + 1);
} else {
// if wrong answer was selected, put a red background
e.target.style.backgroundColor = "red";
}
// just to simulate `delay` since I don't have that util
await new Promise((resolve) => setTimeout(resolve, 1000));
e.target.style.backgroundColor = "";
if (correctAnswerRef.current) {
correctAnswerRef.current.style.backgroundColor = "";
}
// too lazy to implement nextQuestion, so I just increment the question number
setQuestionNumber(questionNumber + 1);
};
const question = questions[questionNumber];
const listOfAnswers = question.possibleAnswers.map((obj) => {
return (
<div>
<Button
key={obj.answer} // ideally each answer should have one (and you should never use index!)
ref={obj.isCorrect ? correctAnswerRef : undefined}
style={{
margin: "15px 0"
}}
variant="contained"
onClick={(e) => handleClick(obj, e)}
>
{obj.answer}
</Button>
</div>
);
});
return (
<div>
<div>{question.question}</div>
{listOfAnswers}
</div>
);
};
const questions = [
{
question: "Is this working?",
possibleAnswers: [
{
answer: "Yes",
isCorrect: true
},
{
answer: "No",
isCorrect: false
}
]
},
{
question: "Is this a good question?",
possibleAnswers: [
{
answer: "Nope",
isCorrect: false
},
{
answer: "Yes, it is!",
isCorrect: true
}
]
}
];
changing state.findIndex to state.ids.findIndex resolved this issue.
However when calling the function onClick it automatically updates ALL of the active values to true. rather than waiting for the click event.
I am trying to set up an onClick method/function that will find the active: true and set it to false and then find the object with the matching id: xxxxxx and set that object's active: to true.
According to developer.mozilla.org and several other sites, my method should theoretically work, or, more likely I'm misinterpreting it.
This is the error it's returning
This is the function I'm trying to write - why does it return that findindex is not a function?
function changeActiveField(im) {
const i = state.findIndex((obj) => obj.active === true);
state[i].active = false;
const index = state.findIndex((obj) => obj.id === im);
state[index].active = true;
}
Here the const i finds the index of the obj{ active: true} and changes it to obj{ active: false}
then const index finds the obj with the obj{id: value} that matches the im which is passed in from the clicked component...
<div className="thumbs">
{state.ids.map((i) => (
<Image
className="carouselitem"
rounded
fluid
onClick={changeActiveField(i.id)}
src={"http://img.youtube.com/vi/" + i.id + "/hqdefault.jpg"}
size="small"
/>
))}
</div>
Go to https://test.ghostrez.net and navigate to the Services page to see the video menu. The small thumbnails are the "clickable" items that should trigger the changeActiveField function in turn setting the clicked id to active: true and changing the activevid div
here is the full page code.
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { Embed, Image, Loader } from "semantic-ui-react";
import "./Player.css";
var state = {
ids: [
{
id: "iCBvfW08jlo",
active: true,
},
{
id: "qvOcCQXZVg0",
active: false,
},
{
id: "YXNC3GKmjgk",
active: false,
},
],
};
function firstActiveId(ids) {
for (var i = 0; i < ids.length; i++) {
if (ids[i].active) {
return ids[i].id;
}
}
}
function changeActiveField(im) {
const i = state.findIndex((obj) => obj.active === true);
state[i].active = false;
const index = state.findIndex((obj) => obj.id === im);
state[index].active = true;
}
export default class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
state: false,
};
}
render() {
return (
<div className="carouselwrap">
<div className="activevid">
<Embed
active
autoplay={false}
color="white"
hd={false}
id={firstActiveId(state.ids)}
iframe={{
allowFullScreen: true,
style: {
padding: 10,
},
}}
placeholder={
"http://img.youtube.com/vi/" +
firstActiveId(state.ids) +
"/hqdefault.jpg"
}
source="youtube"
/>
</div>
<div className="thumbs">
{state.ids.map((i) => (
<>
<Image
className="carouselitem"
rounded
fluid
onClick={changeActiveField(i.id)}
src={"http://img.youtube.com/vi/" + i.id + "/hqdefault.jpg"}
size="small"
/>
<h2>
{/*this is for testing purposes only*/}
{i.id} {i.active ? "true" : "false"}
</h2>
</>
))}
</div>
</div>
);
}
}
the problem was that the state was not the array, rather ids i the array... so changing my statements to state.ids.findIndex solved that problem
function changeActiveField(im) {
const i = state.findIndex((obj) => obj.active === true);
state[i].active = false;
const index = state.findIndex((obj) => obj.id === im);
state[index].active = true;
}
I have a React app with 2 separate components in a parent component: a dropdown component ( SearchDropdown) and an autocomplete textbox component (Autocomplete).
<div className="sidebar" style={{
borderLeft: "1px solid gray"
}}>
<h2 style={{ textAlign: "center", color: "grey" }}>Filter By:</h2>
<hr style={{ color: "grey", marginBottom: "20px" }} />
<form onSubmit={(e) => {
e.preventDefault();
const { state } = autoCompleteRef.current;
const keys = searchDropdownRef.current.state.userInput;
callSearchAPI(state.userInput, keys);
}}>
<SearchDropdown ref={searchDropdownRef}
onSearchDropdownChange={listHandler}
/>
<hr style={{ color: "grey", marginBottom: "20px" }} />
<label style={{ color: "grey" }}>Enter Value</label> <br />
<Autocomplete ref={autoCompleteRef}
suggestions={autoData}
style={{ marginBottom: "10px" }} />
<button className="btn btn-primary" > Search</button>
</form>
</div >
I have a modest goal here. Whenever the dropdown selection changes, I want to clear any text in the Autocomplete textbox. I have tried using a DOM selector in my listHandler() function called in the SearchDropdown component's `onSearchDropdownChange event. This method works in the browser console:
function listHandler(searchKey) {
document.getElementById("autocomplete").value = "";
setKey(searchKey);
const auto_key = { searchKey };
let temp = "";
if (auto_key.searchKey == 'name') {
temp = { namesData };
return setAutoData(temp.namesData);
} else if (auto_key.searchKey == 'title') {
temp = { titleData };
return setAutoData(temp.titleData);
} else if (auto_key.searchKey == 'email') {
temp = { emailData };
return setAutoData(temp.emailData);
} else if (auto_key.searchKey == 'phone') {
temp = { phoneData };
return setAutoData(temp.phoneData);
} else if (auto_key.searchKey == 'county') {
temp = { countyData };
return setAutoData(temp.countyData);
} else if (auto_key.searchKey == 'affiliation') {
temp = { affiliationData };
return setAutoData(temp.affiliationData);
} else {
return setAutoData([]);
}
}
Why doesn't this work in the code but does work in the browser? How can I clear this textbox when the dropdown selection changes?
To resolve this, I first had to add a handleReset function in my Autocomplete component:
class Autocomplete extends Component {
constructor(props) {
super(props);
this.state = {
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: ""
};
}
handleReset = () => {
this.setState({
userInput: ""
});
};
In my parent component, I had already added useRef() to reference my Autocomplete and SearchDropdown components.
export const Table = () => {
//For Sidebar Search
const autoCompleteRef = useRef();
const searchDropdownRef = useRef();
I then called my original listHandler function when the SearchDropdown component selection changed and added my reference to the handleReset function using the AutocompleteRef like this:
function listHandler(searchKey) {
document.querySelector('input[id=autocomplete]').value = "";
autoCompleteRef.current.handleReset();
...
}
It works now.
I am working with React and Vanilla JS. Demo
Main Code
import React from "react";
import { render } from "react-dom";
import { makeData } from "./Utils";
import Select from "react-select";
import "react-select/dist/react-select.css";
// Import React Table
import ReactTable from "react-table";
import "react-table/react-table.css";
import jsondata from "./sample";
class App extends React.Component {
constructor() {
super();
this.state = {
// data: makeData(),
data: jsondata,
filtered: [],
select2: null,
select3: null
};
this.uniqueOptions = this.uniqueOptions.bind(this);
}
onFilteredChangeCustom(value, accessor) {
console.log("The value is " + value);
let filtered = this.state.filtered;
console.log("the filtered items" + JSON.stringify(this.state.filtered));
let insertNewFilter = 1;
if (filtered.length) {
console.log("filtered.length " + filtered.length);
filtered.forEach((filter, i) => {
if (filter["id"] === accessor) {
if (value === "" || !value.length) filtered.splice(i, 1);
else filter["value"] = value;
insertNewFilter = 0;
}
});
}
if (insertNewFilter) {
filtered.push({ id: accessor, value: value });
}
this.setState({ filtered: filtered });
console.log("The filtered data is " + JSON.stringify(this.state.filtered));
}
uniqueOptions = (objectsArray, objectKey) => {
var a = objectsArray.map((o, i) => {
return o[objectKey];
});
return a.filter(function(i, index) {
return a.indexOf(i) >= index;
});
};
render() {
const { data } = this.state;
let first_names = null;
let last_names = null;
let first_names_label = null;
let last_names_label = null;
if (this.state.filtered.length == 1) {
if (this.state.filtered[0].id == "firstName") {
first_names_label = "First Name:";
} else {
first_names_label = "Last Name:";
}
first_names = this.state.filtered[0].value.map(name => <p>{name}</p>);
} else if (this.state.filtered.length == 2) {
first_names_label = "First Name:";
first_names = this.state.filtered[0].value.map(name => <p>{name}</p>);
last_names_label = "Last Name:";
last_names = this.state.filtered[1].value.map(name => <p>{name}</p>);
}
return (
<div>
<pre>
{first_names_label}
{first_names}
{last_names_label}
{last_names}
</pre>
<br />
<br />
Extern Select2 :{" "}
<Select
style={{ width: "50%", marginBottom: "20px" }}
onChange={entry => {
this.setState({ select2: entry });
this.onFilteredChangeCustom(
entry.map(o => {
return o.value;
}),
"firstName"
);
}}
value={this.state.select2}
multi={true}
options={this.uniqueOptions(this.state.data, "firstName").map(
(name, i) => {
return { id: i, value: name, label: name };
}
)}
/>
Extern Select3 :{" "}
<Select
style={{ width: "50%", marginBottom: "20px" }}
onChange={entry => {
this.setState({ select3: entry });
this.onFilteredChangeCustom(
entry.map(o => {
return o.value;
}),
"lastName"
);
}}
value={this.state.select3}
multi={true}
options={this.uniqueOptions(this.state.data, "lastName").map(
(name, i) => {
return { id: i, value: name, label: name };
}
)}
/>
<ReactTable
data={data}
filtered={this.state.filtered}
onFilteredChange={(filtered, column, value) => {
this.onFilteredChangeCustom(value, column.id || column.accessor);
}}
defaultFilterMethod={(filter, row, column) => {
const id = filter.pivotId || filter.id;
if (typeof filter.value === "object") {
return row[id] !== undefined
? filter.value.indexOf(row[id]) > -1
: true;
} else {
return row[id] !== undefined
? String(row[id]).indexOf(filter.value) > -1
: true;
}
}}
columns={[
{
Header: "Name",
columns: [
{
Header: "First Name",
accessor: "firstName"
},
{
Header: "Last Name",
id: "lastName",
accessor: d => d.lastName
}
]
},
{
Header: "Info",
columns: [
{
Header: "Age",
accessor: "age"
}
]
}
]}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
);
}
}
render(<App />, document.getElementById("root"));
I have a filtered state variable (this.state.filtered) which has all the filtered items from dropdown.
I want to show the filtered state variable like this
<div>
<div>id: val0,val1 (and a cross button to remove this filter)</div>
<div>id: val0,val1 (and a cross button to remove this filter)</div> .... so on
</div>
I have been able to do this for 2 variables. But in future I will have 10 to 15 dropdowns. That is why I want to display using a loop. How to do this?
The code I have in my demo do not have loop and I want to make it a generic code which can display all key value pairs of this.state.filtered. Without loop I will not be able to use this functionality when I have a huge number of dropdowns. Also the values should be comma separated. I also do not want to make the render function big and dirty. I want to call a function basically which will show the keys and values
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.