Toggle columns on react-bootstrap-table2 - reactjs

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.

Related

React: best practice for state control

Lately I've noticed that I'm writing a lot of React code were I have 2 components:
Data filter
Data display
In the first component I might have N possible filters to apply to the data. These filters often complex components on their own. When the user defines the filters I need to pass it from 1st component to the 2nd for display.
Example: https://codesandbox.io/s/wonderful-sutherland-16zhp8?file=/src/App.js
What I see in many cases to happen is that I manage the state of the filter in multiple places.
In the example above, I have it in the Toolbar component and in the parent one.
What is the best way of managing states in such case?
You should definitely never duplicate the state in multiple places as this can create bugs where they get out of sync. The solution is to hoist state and then pass it down via props, see https://beta.reactjs.org/learn/sharing-state-between-components.
Here is your codesandbox modified as an example: https://codesandbox.io/s/keen-wilbur-0xyi8f
You are duplicating the sate multiple times, plus there are some unnecessary helper functions along the way.
The state can be held in one place, with its setter function passed to the toolbar component, like so:
export default function App() {
const [searchStr, setSearchStr] = useState("");
return (
<div className="App">
<h1>Tony Starks Nicknames</h1>
<Toolbar searchStr={searchStr} onSearchStrChange={setSearchStr} />
<List searchStr={searchStr} />
</div>
);
}
Then, the toolbar can look like this:
function Toolbar({ searchStr, onSearchStrChange }) {
const handleChange = (e) => {
onSearchStrChange(e.target.value);
};
return (
<div>
<input
type="text"
onChange={handleChange}
value={searchStr}
placeholder="search..."
/>
</div>
);
}
This way, there's only one place where the state is stored
If your hierarchy's depth is or might become greater than 2, I would suggest the Context API.
https://reactjs.org/docs/context.html

How do I put a react jsx component within my ag-grid cells

Unfortunately I cannot share code because it is company confidential but I am basically using colDefs to define my columns within a React ag-grid and would like to have one column whose cells are all a custom JSX button component I built that will allow me to delete the row of the cell clicked as well as propagate changes elsewhere in the code. I have been stuck trying to use cellRenderers and simply cannot figure out how to add custom react functional components to the cell. If anyone can assist with this it would be greatly appreciated. I will try to provide as much additional context as needed but am unfortunately unable to share direct code snippets. Thanks!
You can see an example in ag-grid's documentation here. I've also put up a sandbox in which you can delete rows from the grid by clicking each respective button.
Basically you have to:
Create your custom renderer that will appear in the cells of the column, like the DeleteCellRenderer. You'll have to access at least 2 props:
props.context, the grid's context which will contain the method(s) to fire in the onClick method
props.data, which contains the data for that row - the specific item in the rowData array that is.
Open the component that renders the <AgGridReact /> component and import the renderer.
Declare the renderer in the grid's frameworkComponents prop, like this:
<AgGridReact
frameworkComponents={{
deleteCellRenderer: DeleteCellRenderer
}}
// ...
Declare your delete function to fire when clicking on the button, then pass it to the grid's context.
const handleDelete = (data) => {
// Your logic here
};
// ...
<AgGridReact
frameworkComponents={{
deleteCellRenderer: DeleteCellRenderer
}}
context={{ handleDelete }}
//...
Finally, insert the column in the colDef array containing the cellRenderer, either like this:
const colDef = [
//...
{
headerName: "delete"
cellRenderer: "deleteCellRenderer"
},
//...
];
Or, if you're using <AgGridColumn> components as children:
<AgGridReact
//...
>
<AgGridColumn headerName="Delete" cellRenderer="deleteCellRenderer" />
//...
</AgGridReact>

Is it possible to add data to a React component using the same prop label more than once, then have the component map that data into list items?

I'd like to create a reusable component in React that displays a list of what payment methods are available for a certain service.
Is it possible to add the data to the component like this (with multiple uses of the same prop label)?
const Page = () => {
<PaymentMethods method="BACS Transfer" method="Cheque" method="PayPal" method="Credit / Debit Card" />
}
Or perhaps it's an array method=[BACS Transfer, Cheque]
Either way, I'd like to know if it's possible. If so, what does the component look like to map over the data and output as <li> items.
If this is a bad approach, what is the best way to do it?
Thanks in advance!
Pass them as an array, like this:
<PaymentMethods methods={["BACS Transfer", "Cheque", "PayPal", "Credit / Debit Card"]} />
The component itself should be something such as this:
const PaymentMethods = ({methods}) => {
return <ul>
{methods.map((method, index) => <li key={index}>{method}</li>)}
</ul>
}
There are many ways to achieve it.
put the items on one prop
<PaymentMethods
lists={['BACS Transfer', 'Cheque', 'PayPal', 'Credit/Debit Card']}
/>
////
function PaymentMethods({lists}){
return (
<ul>
{lists.map((text)=>(<li>{text}</li>))}
</ul>
)
}
Using children
<PaymentMethods>
<li>BACS Transfer</li>
<li>Cheque</li>
<li>PayPal</li>
<li>Credit/Debit Card</li>
</PaymentMethods>

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.

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

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

Resources