FlatList item doesn't update when I setState() - javascript

I'm building multiple select modal. When user press the item, the item should be marked as 'Checked'.
Problem I added/removed id from id arrays. When I open and check modal, it doesn't show 'Check' sign. But when I close and open the modal again, it shows 'Check' Sign.
To keep track of selected items, I defined the items in the modal component's state.
state = {
selectedSeasonIds: this.props.selectedSeasonIds,
}
Here is react-native-modal which I use to show modal on the screen
<Modal
isVisible={isSelectorVisible}
onBackdropPress = {() => this.props.hideSelector()}>
<View style={styles.modalContainer}>
<FlatList
style={styles.root}
data={this.props.items}
ItemSeparatorComponent={this._renderSeparator}
keyExtractor={this._keyExtractor}
renderItem={this._renderItemForMultiple}/>
</View>
</Modal>
This is render function for each item
_renderItemForMultiple = ({item}) => {
return (
<TouchableOpacity
style={styles.itemStyle}
onPress={() => {this._handleMultipleItemPress(item.id)}}>
<RkText>{item.value}</RkText>
{ this._renderCheck(item.id) } <<< Here is the problem
</TouchableOpacity>
);
}
When user clicks the item, FlatList's item calls _handleMultipleitemPress
_handleMultipleItemPress = (id) => {
let { selectionType } = this.props;
let { selectedSeasonIds, selectedSizeIds, selectedColorIds } = this.state;
if(selectionType===2) {
if(_.includes(this.state.selectedSeasonIds, id)) {
let newSelectedSeasonIds = _.filter(this.state.selectedSeasonIds, (curObject) => {
return curObject !== id;
});
this.setState({selectedSeasonIds : newSelectedSeasonIds});
} else {
let newSelectedSeasonIds = [...this.state.selectedSeasonIds, id];
this.setState({selectedSeasonIds : newSelectedSeasonIds});
}
}
// season Select Action
this.props.seasonSelectAction(id);
}
Problem We added/removed id from id arrays. When I open and check modal, it doesn't show 'Check' sign. But when I close and open the modal again, it shows 'Check' Sign.
Somehow the modal is not rendered even eventhough we setState in renderCheck(). Why is it happening? And How can I fix it?
_renderCheck = (id) => {
let { selectionType, selectedSeasonIds, selectedSizeIds, selectedColorIds } = this.props;
if(selectionType===2) {
if(_.includes(this.state.selectedSeasonIds, id)) {
return (<RkText>Check </RkText>);
}
}
return (<RkText> </RkText>);
}
Any other advice will be also appreciated! Thanks for reading this post.
UPDATE I debugged with code and when I press the item, it doesn't go through _renderItemForMultiple. I think it's because I didn't define a param for _renderItemForMultiple. How can I pass item to its param? Any idea?

Even though your state changes, you're not passing it to <FlatList>, so its props don't change. Its shouldComponentUpdate method returns false when none its props change. As the docs state:
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
So you need to pass extraData={this.state} to FlatList.

Related

handling a select all in react and materialUI

I have several sets of checkboxes that should work independently and a toggle that toggles them all (within their groups only). I have two states (one for the toggle and one for the checkboxes). I can identify the checkbox I'm clicking with the event.target.value, that way I can manipulate it individually. But I'm having trouble controlling them all at once with the toggle as well as making the toggle come active when someone independently checks them all true.
In summary
1- when the toggle is on, all checkboxes within its group come on, same for off
2- When I turn on each checkbox individually until they are all on, the toggle turns on and when I uncheck one of them, the toggle turns off
I've made a sandbox for you to play on. Thanks in advance
const [active, setActive] = useState(false)
const [singleactive, setSingleActive] = useState([])
const handleSwitch = (e) => {
if(e.target.value === "Select All") {
setActive(e.target.checked)
setSingleActive([...singleactive])
} else {
setSingleActive([])
}
}
const handleSingleSwitch = (e) => {
const index = singleactive.indexOf(e.target.value)
if(index === -1) {
setSingleActive([...singleactive, e.target.value])
} else {
setSingleActive(singleactive.filter((singleactive) => singleactive !== e.target.value))
}
}
Well, I figure it out. Though I find answering your own question a bit pretentious lol, here it is in case it helps
Updated states and functions
const [active, setActive] = useState(false)
/// fill a new array with false values for the length of the data to load them all unchecked
const [checkedState, setCheckedState] = useState(new Array(data.length).fill(false));
const handleSwitch = (e) => {
if(e.target.checked) {
/// if the toggle event comes back checked, set the active state to true and re-fill the array with all true values which is what Select All does
setActive(true)
setCheckedState(new Array(data.length).fill(true));
} else {
/// if the toggle event comes back unchecked, set the active state to false and re-fill the array with all false values which is what Deselect All does
setActive(false)
setCheckedState(new Array(data.length).fill(false));
}
}
const handleOnChange = (position) => {
// every time you click an infividual checkbox, map through the state and compare its index with the position in the array. If it's true, make it true otherwise false; then set the state with this value
const updatedCheckedState = checkedState.map((item, index) => {
return (
index === position ? !item : item
)}
);
setCheckedState(updatedCheckedState);
/// if the new generated array of values contains at least one false in it, set the active class on the toggle to false, but if there isn't at least one false, then all are true, so set the active class to true on the toggle
if(updatedCheckedState.includes(false)) {
setActive(false)
} else {
setActive(true)
}
};
I also removed the value on the toggleAll checkbox that I inadvertently set statically to Select All. This way I can control it via state
<Stack direction="row" spacing={1} alignItems="center">
<Typography>Deselect All</Typography>
<Switch
size="small"
checked={active}
onChange={handleSwitch} />
<Typography>Select All</Typography>
</Stack>
And lastly the checkboxes
<FormControlLabel
control={
<Checkbox
size="small"
name={item.toLowerCase()}
value={item.toLowerCase()}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
}
label= {item.replaceAll('_', ' ')} />
Check the sandbox for the updated code.

How to scroll between sibling components in React?

I want to be able to scroll between table components in my React app. I have created a component for a table called FormattedTable which takes in props and displays all the information that I want.
A lot of the tables refer to other tables with clickable text. If you click on a reference to another table and it is not being displayed, I add the table to the display and the app automatically scrolls down to the bottom of the screen where the table has been added. However, if the table is already being displayed, I want the app to scroll to where it is being displayed already.
The clicking on the reference and adding another table all occurs in the FormattedTable.js file.
In my Home.js I have an array of objects called selected and this array contains all the objects that I want to be displayed in tables. I display the tables by mapping through the selected array creating a FormattedTable component on each iteration.
Home.js
<div className="rightColumn" style={{flex: 4}}>
{selected.length > 0 ? selected.map((obj, index) => {
return (
<div style={{width: '60%'}}>
<FormattedTable data={data} selected={selected} obj={obj} index={index} onSelectedChange={setSelected}/>
</div>
)
})
: null}
</div>
Because the FormattedTables are being created dynamically in the Home.js file, I'm not sure how to scroll from one table to another in FormattedTable.js (since there is only 1 file but multiple instances).
Does anyone know how this would be possible to do in the FormattedTable.js file?
What I've tried so far is added a ref to the div that's being dynamically created in Home.js and also passed in a triggerScroll method to the FormattedTable component so that I can trigger the scroll when a reference is clicked on a table. The issue with this though is that it still scrolls to the last element as the value of the ref is (naturally) the last element of the array when the mapping stops.
<div className="rightColumn" style={{flex: 4}}>
{selected.length > 0 ? selected.map((obj, index) => {
return (
<div ref = {scrollRef} style={{width: '60%'}}>
<FormattedTable data={data} selected={selected} obj={obj} index={index} onSelectedChange={setSelected} triggerScroll={scrollToTable}/>
</div>
)
})
: null}
</div>
Fixed myself:
Added an attribute to each object in the selected array called inFocus. The most recently selected object in the array has a value of true for this attribute.
I then added a ternary to setting the ref of the FormattedTable based on the inFocus attribute so only one object will be set as the ref at a time.
FormattedTable.js
//For selecting results
const select = (name) => {
//Deep clone object so that results doesn't change when selected changes
const obj = cloneDeep(data.find(element => element.name === name));
const refObj = data.find(element => element.name === name);
//If object is in the original JSON array
if(typeof refObj !== 'undefined') {
//If object is not in the selected array, add it to selected array
if(selected.find(element => element.name === name) === undefined) {
//Make the new object in focus and remove the focus for all the other objects
obj.inFocus = true;
copy.map((el) => {
if(el.name !== obj.name) {
el.inFocus = false
}
})
handleSelectedChange([...copy, obj]);
}
//Otherwise, set focus to selected object
else {
//Make the selected object in focus and remove the focus for all the other objects
copy.map((el) => {
if(el.name !== obj.name) {
el.inFocus = false
} else {
el.inFocus = true
}
})
handleSelectedChange([...copy]);
}
}
}
return (
<div ref={obj.inFocus ? messagesEndRef : null} style={{display: 'flex', flexDirection: 'row'}}>
...
)

Dismount and re-mount a component on each button click?

I want to conditionally render a custom notification component (not 100% sure how it works, it was written by others and it uses material-ui). I want this notification to appear every time a button gets clicked, but for some reason the notification component will stop re-appearing after it auto-hides itself after some time, even when I click the re-click the button - again, I'm not totally sure how this works.
I just want to know if there's a way to dismount and then re-mount this custom notification component so that it gets newly rendered on each button click (is that bad practice?). This is a simplified ver. of my current logic:
const RandComponent = ()=>{
const [notifType, setNotifType] = useState(1);
const toggleNotif = ()=>{
setNotifType(!notifType);
}
const getNotif = ()=>{
return <Notification type={notifType}/>
}
return (<>
....
<button onClick={toggleNotif}>TOGGLE</button>
{getNotif(notifType)}
</>)
}
You can do it like this
const RandComponent = () => {
const [showNotification,setShowNotification] = useState(true):
const toggleNotificationHandler = () => {
setShowNotification(p => !p);
}
return (<>
<button onClick={toggleNotificationHandler}>TOGGLE</button>
{showNotification && <Notification />}
</>)
}

React rendering another table row with click dynamic

Hey Stack OverFlow community I am working on a React project where I am mapping over a set of table rows. Within every table row I have an additional row with more info about each individuals rows data. My issue is that when I click on the button to render additional information for that table it renders all of the additional informations for all of the rows.
I understand that my logic is implemented in a way where every single additional row will show upon a click. What can I do to fix this?
https://codesandbox.io/s/rj8o4r493n
showDrawyer = () => {
let {showDrawyer} = this.state
this.setState({
showDrawyer: !showDrawyer
})
}
renderTableCellData = () => {
let { tableData } = this.props;
return tableData.map((data, index) => {
return (
<Table.Body>
<Table.Row style={{ height: 75 }}>
<Table.Cell onClick={this.showDrawyer}>{data.name}</Table.Cell>
<Table.Cell>{data.number}</Table.Cell>
<Table.Cell>{data.date}</Table.Cell>
<Table.Cell>{data.uid}</Table.Cell>
</Table.Row>
<Table.Row style={{display: this.state.showDrawyer ? '' : 'none' }}>
<Table.Cell>Hidden Row data</Table.Cell>
</Table.Row>
</Table.Body>
)
})
}
state={
shownDrawerIndex:null
}
showDrawyer = (index) => {
this.setState({
shownDrawerIndex:index
})
}
renderTableCellData = () => {
let { tableData } = this.props;
return tableData.map((data, index) => {
return (
<Table.Body>
<Table.Row style={{ height: 75 }}>
<Table.Cell onClick={()=>this.showDrawyer(index)}>{data.name}</Table.Cell>
<Table.Cell>{data.number}</Table.Cell>
<Table.Cell>{data.date}</Table.Cell>
<Table.Cell>{data.uid}</Table.Cell>
</Table.Row>
<Table.Row style={{display: this.state.shownDrawerIndex == index ? '' : 'none' }}>
<Table.Cell>Hidden Row data</Table.Cell>
</Table.Row>
</Table.Body>
)
})
}
You will have to pass the index of the row on click.This will set the state to that index.
React will re-render the component on set state. While doing this it will check for the drawer index value in state.
According to that state value, it will display and hide the drawer
The solution depends on whether you want (1) additional details to be displayed for multiple rows, or whether (2) once you click on one row, only this row's additional details will be shown, and hidden for the one clicked before.
For (1) Add to the tableData array a showDrawyer field which will be tested in order to know whether to display or not to display the additional info for this element.
OnClick should get as parameter the clicked array element and should toggle this element's showDrawyer value.
For (2) - the state variable that decides which row's additional details are displayed will be an index, rather than a toggle. This index will be checked for the additional details display.

Array.map item to a state

How do I get the values of array.map outside of it?
For example:
array.map(index, item) => { return ( <Text>{item.ID}</Text> ) }
Ouside of it I have something like:
<Button onPress={() => this.editItem(CURRENT_ITEM_ID)}></Button>
Where CURRENT_ITEM_ID is the current item on a Card, for example.
Like Tinder I have an array of Cards to Swipe, that I'm mapping, but the Like button is not part of the Card. This way I'm not moving the Like button with the Card, instead it is static, I'm just moving the content of the card. So I need a way to access the current item from outside of the map. The Card has Text and ID. The component below have a Button that I need to click passing the item.ID of the current Card.
Is there a way to set this item to a state?
Thank you.
One solution is to create a state property that holds the id of the card that is showing, and then when the button is clicked, you grab that state and do something with it. Here is an example with onClicks and divs.
const arr = ['card1', 'card2', 'card3', 'card4'];
class App extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleClick(index) {
this.setState({ visibleCard: index });
}
handleButtonClick() {
console.log('this.state.visibleCard', this.state.visibleCard);
console.log('visibleCard', arr[this.state.visibleCard]);
}
render() {
return (
<div>
{arr.map((card, i) => <div onClick={() => this.handleClick(i)}>{card}</div>)}
<button onClick={this.handleButtonClick}>test</button>
</div>
)
}
}
The basic idea is that you tie the index to the card. A handler then sets that state to visible(not sure what this would be in your case since it seems like a mobile app). Then in your button, you use the state to grab the visible card and pass the data to whatever other function you need.
What I normally do is bind the event in the map function.
array.map( (item, key) => {
return (
<div>
<Text>{item.ID}</Text>
<button onClick={this.editItem.bind(this, item.ID)}>Edit</button>
</div>
)
}
Not sure what your HTML looks like, but you need to bind the function params in the map.

Categories