Is render method of a component run in react-virtualized rowRenderer - reactjs

I am using a List provided by react-virtualized. Inside the rowRenderer I return a more complex component. This component has some events defined for it. When an event is triggered, a part of the this component should be updated with new structure, e.g. a new input is rendered inside the row. This doesn't not seem to work when used inside the List.
<List height={height} width={1800} rowCount={this.state.leads.length} rowHeight={rowHeight} rowRenderer={this.rowRenderer} />
Here's the rowRenderer:
rowRenderer(props) {
let opacityvalue = 1
if (this.state.deleted.indexOf(props.index) >= 0) {
opacityvalue = 0.3
}
return (
<LeadItem {...props}
lead={this.state.leads[props.index]}
leadKey={props.index}
...
/>
)}
Here's the element that should show up when a specific event is triggered:
{self.props.lead.show ? <Selectize
queryfield={'tags'}
handleChange={(e) => this.props.updateLeadData(self.props.leadKey, 'tags', 'update', e)}
value={self.props.lead.tags}
create={false}
persist="false"
multiple
options={self.props.TAGS}
/> : <div>{taglist}</div>}
EDIT: Here's a simple example where I prove my point.
https://codesandbox.io/s/2o6v4my7pr
When user presses on the button, there must appear a new element inside the row.
UPDATE:
I see now that it's related to this:
https://github.com/bvaughn/react-virtualized#pure-components

I think you can achieve what you're trying to do by passing through this property to List as mentioned in the docs:
<List
{...otherListProps}
displayDiv={this.state.displayDiv}
/>

Related

Why does removing the correct item in state array, not delete the right component in the UI? - React

When I console.log the index that is being deleted it shows the correct index to be deleted, but the behaviour does not correspond - it always only deletes the last item in the array on the UI. (but in the state array it is deleting the correct index)
example:
(refer to image below)
When I click on delete for the first item (index 0), it removes the last item (index 3) from the UI. But upon viewing the console.log, it actually removed the correct item (index 0) from the state array.
How it looks in the UI:
Note that the red numbers are there just for me to know if the array index is correct. (and it is working correctly)
EditUser.js
// holds the array of data for each RoleMapInput component
const [roleMaps, setRoleMaps] = useState([{organisation:[], roles:[], type:[]}]);
// Used to add a RoleMapInput component to the UI
const handleAddRoleMap = () =>{
setRoleMaps((oldRoleMaps) => [...oldRoleMaps,{organisation:[], roles:[], type:[]}])
}
// Used to delete a RoleMapInput component from the UI
const handleDelRoleMap = (delIndex) =>{
console.log("delIndex: ", delIndex)
setRoleMaps((oldRoleMaps) => {
console.log(oldRoleMaps)
return oldRoleMaps.filter((_,index)=>index !== delIndex)
})
...
...
...
return(
...
// Where the RoleMapInput component is duplicated based on the array roleMaps
{console.log("roleMaps at grid: ",roleMaps)}
{roleMaps.map((roleMap, index) => (
<RoleMapInput key={index} roleMapIndex={index} roleMap={roleMap} handleDelRoleMap={handleDelRoleMap}/>
))}
...
)
RoleMapInput.js
export default function RoleMapInput(props) {
...
...
return (
<>
<Grid item xs={1}>
<FormControl fullWidth>
<IconButton aria-label="delete" color='error' onClick={()=>props.handleDelRoleMap(props.roleMapIndex)}>
{props.roleMapIndex}
<DeleteOutlineOutlinedIcon/>
</IconButton>
</FormControl>
</Grid>
</>
);
}
Without the minimum reproducible example is really hard to tell. But I can tell you this: the filter function is working properly, but using as react docs mention:
We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state.
You can read more about this here
A possible explanation is that when you are removing the element on pos 0 react might consider that it doesn't need to re-render any child component, but rather remove the last element (because you had rendered <RoleMapInput components with keys 0,1,2,3 and after removing any item from the array you are left with keys 0,1,2)

How to get a reference to a DOM element that depends on an input parameter?

Say I have the standard TODO app, and want a ref to the last item in the list. I might have something like:
const TODO = ({items}) => {
const lastItemRef = Reeact.useRef()
return {
<>
{items.map(item => <Item ref={item == items.last() ? lastItemRef : undefined} />)}
<>
}
}
But this doesn't seem to work - after lastItemRef is initialized, it is never subsequently updated as items are added to items. Is there a clean way of doing this without using a selector?
I think in your case it depends upon how the items list is updated. This is because useRef won't re-render the component if you change its current attribute (persistent). But it does re-render when you choose, for example, useState.
Just as a working case, see if this is what you were looking for.
Ps: check the console

Toggle columns on react-bootstrap-table2

Using this library https://react-bootstrap-table.github.io/react-bootstrap-table2/
And this to toggle columns: https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Bootstrap%204&selectedStory=Column%20Toggle%20with%20bootstrap%204&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel
Docs on column toggle: https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-column-toggle.html
I need to know what columns have been hidden.
A callback is included for this:
onColumnToggle: Call this method when user toggle a column.
Implemented:
<ToolkitProvider
keyField="globalId"
data={ this.props.data }
columns={ this.state.columns }
columnToggle
>
{
props => {
return (
<>
<ToggleList {...props.columnToggleProps} onColumnToggle={this.columnToggle} className="d-flex flex-wrap"/>
<hr/>
<BootstrapTable
striped
bootstrap4
keyfield="globalId"
{...props.baseProps}
/>
</>
)
}
}
</ToolkitProvider>
My function this.columnToggle fires as expected. But the table itself is no longer hiding/showing columns. If I remove my function, it works again.
Updated:
The columnToggle function:
columnToggle = (column) => {
console.log(column); // outputs the toggled column
};
the ToggleList uses the render props design pattern, so it sends the original onColumnToggle
with the props you spread on the component ToggleList, but also, you provided your own copy of the onColumnToggle function, which will override the expected result.
a simple solution so you could take advantage of the two functionalities (the actual onColumnToggle of the Component, and your copy of it) by doing something like this:
<ToggleList {...props.columnToggleProps} onColumnToggle={() => {this.columnToggle(); props.columnToggleProps.onColumnToggle(/* whatever params it needs */)}} className="d-flex flex-wrap"/>
this will let you do custom things when the column toggles, and you still have the original functionality of the ToggleList API.
EDIT: The Problem with this solution, that the ToggleList component seems to be Un-controlled. so I would suggest using this example from the official docs.

How do I call an event handler or method in a child component from a parent?

I'm trying to implement something similar to the Floating Action Button (FAB) in the Material-UI docs:
https://material-ui.com/demos/buttons/#floating-action-buttons
They have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>Item One</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab>{fab.icon}</Fab>
</Zoom>
));
}
I have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent />
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={ListOfThingsComponent.Add???}>
Add Item to List Component
</Fab>
</Zoom>
));
}
My ListOfThingsComponent originally had an Add button and it worked great. But I wanted to follow the FAB approach for it like they had in the docs. In order to do this, the Add button would then reside outside of the child component. So how do I get a button from the parent to call the Add method of the child component?
I'm not sure how to actually implement the Add Item to List click event handler given that my list component is inside the tab, while the FAB is outside the whole tab structure.
As far as I know I can either:
find a way to connect parent/child to pass the event handler through the levels (e.g. How to pass an event handler to a child component in React)
find a way to better compose components/hierarchy to put the responsibility at the right level (e.g. remove the component and put it in the same file with this in scope using function components?)
I've seen people use ref but that just feels hacky. I'd like to know how it should be done in React. It would be nice if the example went just a bit further and showed where the event handling should reside for the FABs.
thanks in advance, as always, I'll post what I end up doing
It depends on what you expect the clicks to do. Will they only change the state of the given item or will they perform changes outside of that hierarchy? Will a fab be present in every single Tab or you're not sure?
I would think in most cases you're better off doing what you were doing before. Write a CustomComponent for each Tab and have it handle the FAB by itself. The only case in which this could be a bad approach is if you know beforehand that the FAB's callback will make changes up and out of the CustomComponent hierarchy, because in that case you may end up with a callback mess in the long run (still, nothing that global state management couldn't fix).
Edit after your edit: Having a button call a function that is inside a child component is arguably impossible to do in React (without resorting to Refs or other mechanisms that avoid React entirely) because of its one-way data flow. That function has to be somewhere in common, in this case in the component that mounts the button and the ListOfThings component. The button would call that method which would change the state in the "Parent" component, and the new state gets passed to the ListOfThings component via props:
export default class Parent extends Component {
state = {
list: []
};
clickHandler = () => {
// Update state however you need
this.setState({
list: [...this.state.list, 'newItem']
});
}
render() {
return (
<div>
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent list={this.state.list /* Passing the state as prop */}/>
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={this.clickHandler /* Passing the click callback */}>
Add Item to List Component
</Fab>
</Zoom>
))
}
</div>
)
}
}
If you truly need your hierarchy to stay like that, you have to use this method or some form of global state management that the ListOfThingsComponent can read from.

Component is not re rendering after state change in react

While working on my project I faced this issue and tried for two days to solve it. Finally I decided to post it here.
I am rendering array by calling child component as follows:-
Parent component
{this.state.list.map((item,index)=>{
return <RenderList
insert={this.insert.bind(this,index)}
index={index}
length={this.state.list.length}
EnterPressed={this.childEnterPressed.bind(this,index)}
list={item}
onchange={this.childChange.bind(this)}
editable={this.state.editable[index]}
key={index}
onclick={this.tagClicked.bind(this,index)} />
})}
and
Child component
<Draggable grid={[37, 37]}
onStop={this.handleDrop.bind(this)}
axis="y"
onDrag={this.handleDrag.bind(this)}
bounds={{top: -100, left: 0, right: 0, bottom: 100}}
>
<div onClick={this.tagClicked.bind(this)}>
{ this.props.editable ? (<Input
type="text"
label="Edit Item"
id="listItem"
name="listItem"
onKeyPress={this.handleKeyPress.bind(this)}
defaultValue={this.props.list}
onChange={this.onChange.bind(this)} />) :
(<Chip>{this.props.list}</Chip>) }
</div>
</Draggable>
I am using react-draggble to make each individual item of my list draggble. When ot is dragged in handleDrag function I am just updating my state of distance by which component is draged and in handleDrop function I am calling insert function which delete the dragged item from its current position and insert at position where it is dropped.
insert=function(index,y,e){
if(y!==0){
var list=this.state.list;
var editable = Array(this.state.editable.length).fill(false);
if(y/37>0){
var temp=list[index];
var i=0;
for(i=index;i<index+y/37;i++){
list[i]=this.state.list[i+1];
}
list[(y/37)+index]=temp;
this.setState({
...this.state,
list:list,
editable:editable
})
}
}
State is updating correctly. I have tested it but component is not re rendering. what surprised me is that if I just display the list in the same component then it re render correctly.any help is appreciated!
React cannot keep track of the items, as your are using the index as key (which is discouraged). Try to create a unique key based on something like a generated uuid which sticks to the item.
When dragging an item, the position (index) in the array changes. This prevents React from syncing the correct elements. Prop/state changes may not propagate as wanted.

Resources