React.js Material-Table pass selected rows to another page - javascript

How to redirect to another page with passing selected row data as a prop?
I'm using material-table and I want to pass the selected rows data to another page after clicking the "Export" button, so I can use that data to create some kind of report in another page.
I think I should use history.push() method but it's not working in the onClick method. Can someone please give me any hint?
import React from 'react'
import MaterialTable from 'material-table';
class LeadTable extends React.Component{
constructor(props) {
super(props);
this.state = {
leads : [],
};
}
componentDidMount() {
fetch('http://localhost:5000/api/Leads')
.then(res => res.json())
.then((data) => {
// console.log('Data: ', data[0])
this.setState({
leads: data[0]
})
})
.catch(console.log);
}
redirectToReport = () => {
const { history } = this.props;
history.push('report');
}
render(){
return (
<div style={{ maxWidth: '100%' , align: 'center'}}>
<MaterialTable
title="Reporting"
columns={[
...
]}
data = {this.state.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Export the selected activities!',
icon: 'Export',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
// rowData has all the selected row and I want to redirect to another page with passing those data.
}
}]}
/>
</div>
)}
}
export default LeadTable

This answer mainly addresses OP's code base which is using class components. If you are using function components you can use react-router hooks such as useHistory
Use withRouter HOC to enable LeadTable component access to history so you can push
const LeadTableWithRouter = withRouter(LeadTable);
Pass object to push function to pass row data
redirectToReport = (rowData) => {
const { history } = this.props;
history.push({
pathname: "/report", // re-route to this path
state: { name: rowData.name, surname: rowData.surname } // your row data
});
};
In your other component, use this.props.location.state.<data_name> to access the row data you've passed
class AnotherPage extends React.Component {
render() {
return (
<>
<p>{this.props.location.state.name}</p>
<p>{this.props.location.state.surname}</p>
<Link to="/">go back</Link>
</>
);
}
}

Related

React HOC working on some but not other components

I'm using a HOC component to bind an action to many different types of element, including SVG cells, which, when an onClick is bound normally, it works, but when I use my HOC it returns un-intended results.
Minimally reproducible example: https://codesandbox.io/s/ecstatic-keldysh-3viw0
The HOC component:
export const withReport = Component => ({ children, ...props }) => {
console.log(Component); //this only prints for ListItem elements for some reason
const { dispatch } = useContext(DashboardContext);
const handleClick = () => {
console.log('clicked!'); //even this wont work on some.
const { report } = props;
if (typeof report === "undefined") return false;
dispatch({ type: SET_ACTIVE_REPORT, activeReport: report });
dispatch({ type: TOGGLE_REPORT });
};
return (
<Component onClick={handleClick} {...props}>
{children}
</Component>
);
};
Usage working:
const ListItemWIthReport = withReport(ListItem); //list item from react-mui
{items.map((item, key) => (
<ListItemWithReport report={item.report} key={key} button>
{/* listitem children*/}
</ListItemWithReport>
))}
Usage not working:
const BarWithReport = withReport(Bar); //Bar from recharts
{bars.map((bar, index) => (
<BarWithReport
report={bar.report}
key={index}
dataKey={bar.name}
fill={bar.fill}
/>
))}
The ListItem works 100% as anticipated, however, the bars will not render inside of the BarChart. Similarly, with a PieChart the Cells will actually render, with the correct sizes according to their values, however, props like "fill" do not appear to pass down.
Am I using the HOC incorrectly? I don't see an option other than HOC for the inside of Charts as many types of elements will be considered invalid HTML?
You might be dealing with components that have important static properties that need to be hoisted into the wrapped component or need to have ref forwarding implemented in order for their parent components to handle them. Getting these pieces in place is important, especially when wrapping components where you don't know their internals. That Bar component, for example, does have some static properties. Your HOC is making those disappear.
Here's how you can hoist these static members:
import hoistNonReactStatic from 'hoist-non-react-statics';
export const withReport = Component => {
const EnhancedComponent = props => {
const { dispatch } = useContext(DashboardContext);
const handleClick = () => {
const { report } = props;
if (typeof report === "undefined") return false;
dispatch({ type: SET_ACTIVE_REPORT, activeReport: report });
dispatch({ type: TOGGLE_REPORT });
};
return (
<Component onClick={handleClick} {...props}/>
);
};
hoistNonReactStatic(EnhancedComponent, Component);
return EnhancedComponent;
};
Docs on hoisting statics and ref forwarding can be found in this handy guide to HOCs.
There may be some libraries that can take care of all these details for you. One, addhoc, works like this:
import addHOC from 'addhoc';
export const withReport = addHOC(render => {
const { dispatch } = useContext(DashboardContext);
const handleClick = () => {
const { report } = props;
if (typeof report === "undefined") return false;
dispatch({ type: SET_ACTIVE_REPORT, activeReport: report });
dispatch({ type: TOGGLE_REPORT });
};
return render({ onClick: handleClick });
});
Of course, if the parent component is checking child components by type explicitly, then you won't be able to use HOCs at all. In fact, it looks like recharts has that issue. Here you can see the chart is defined in terms of child components which are then searched for explicitly by type.
I think your HOC is invalid, because not every wrapper-Component (e.g. HTML element) is basically clickable. Maybe this snipped can clarify what I am trying to say:
const withReport = Component => (props) => {
const handleClick = () => console.log('whatever')
// Careful - your component might not support onClick by default
return <Component onClick={handleClick} {...props} />
// vs.
return <div onClick={handleClick} style={{backgroundColor: 'green'}}>
<Component {...props} />
{props.children}
</div>
}
// Your import from wherever you want
class SomeClass extends React.Component {
render() {
return <span onClick={this.props.onClick}>{this.props.children}</span>
// vs.
return <span style={{backgroundColor: 'red'}}>
{
// Careful - your imported component might not support children by default
this.props.children
}
</span>
}
}
const ReportedListItem = withReport(SomeClass)
ReactDOM.render(<ReportedListItem>
<h2>child</h2>
</ReportedListItem>, mountNode)
You can have the uppers or the unders (separated by vs.) but not crossed. The HOC using the second return (controlled wrapper-Component) is sure more save.
I've used 4 methods successfully to wrap Recharts components.
First Method
Wrap the component in a HOC and use Object.Assign with some overloads. This breaks some animation and difficult to use an active Dot on lines. Recharts grabs some props from components before rendering them. So if the prop isn't passed into the HOC, then it won't render properly.
...
function LineWrapper({
dataOverload,
data,
children,
strokeWidth,
strokeWidthOverload,
isAnimationActive,
dot,
dotOverload,
activeDot,
activeDotOverload,
...rest
}: PropsWithChildren<Props>) {
const defaultDotStroke = 12;
return (
<Line
aria-label="chart-line"
isAnimationActive={false}
strokeWidth={strokeWidthOverload ?? 2}
data={dataOverload?.chartData ?? data}
dot={dotOverload ?? { strokeWidth: defaultDotStroke }}
activeDot={activeDotOverload ?? { strokeWidth: defaultDotStroke + 2 }}
{...rest}
>
{children}
</Line>
);
}
export default renderChartWrapper(Line, LineWrapper, {
activeDot: <Dot r={14} />,
});
const renderChartWrapper = <P extends BP, BP = {}>(
component: React.ComponentType<BP>,
wrapperFC: React.FC<P>,
defaultProps?: Partial<P>
): React.FC<P> => {
Object.assign(wrapperFC, component);
if (defaultProps) {
wrapperFC.defaultProps = wrapperFC.defaultProps ?? {};
Object.assign(wrapperFC.defaultProps, defaultProps);
}
return wrapperFC;
};
Second Method
Use default props to assign values. Any props passed into the HOC will be overridden.
import { XAxisProps } from 'recharts';
import { createStyles } from '#material-ui/core';
import { themeExtensions } from '../../../assets/theme';
const useStyles = createStyles({
tickStyle: {
...themeExtensions.font.graphAxis,
},
});
type Props = XAxisProps;
// There is no actual implementation of XAxis. Recharts render function grabs the props only.
function XAxisWrapper(props: Props) {
return null;
}
XAxisWrapper.displayName = 'XAxis';
XAxisWrapper.defaultProps = {
allowDecimals: true,
hide: false,
orientation: 'bottom',
width: 0,
height: 30,
mirror: false,
xAxisId: 0,
type: 'category',
domain: [0, 'auto'],
padding: { left: 0, right: 0 },
allowDataOverflow: false,
scale: 'auto',
reversed: false,
allowDuplicatedCategory: false,
tick: { style: useStyles.tickStyle },
tickCount: 5,
tickLine: false,
dataKey: 'key',
};
export default XAxisWrapper;
Third Method
I didn't like this so I've worked around it, but you can extend the class.
export default class LineWrapper extends Line {
render(){
return (<Line {...this.props} />
}
}
Fourth Method
I don't have a quick example of this, but I always render the shape or children and provide functions to help. For example, for bar cells I use this:
export default function renderBarCellPattern(cellOptions: CellRenderOptions) {
const { data, fill, match, pattern } = cellOptions;
const id = _uniqueId();
const cells = data.map((d) =>
match(d) ? (
<Cell
key={`cell-${id}`}
strokeWidth={4}
stroke={fill}
fill={`url(#bar-mask-pattern-${id})`}
/>
) : (
<Cell key={`cell-${id}`} strokeWidth={2} fill={fill} />
)
);
return !pattern
? cells
: cells.concat(
<CloneElement<MaskProps>
key={`pattern-${id}`}
element={pattern}
id={`bar-mask-pattern-${id}`}
fill={fill}
/>
);
}
// and
<Bar {...requiredProps}>
{renderBarCellPattern(...cell details)}
</Bar>
CloneElement is just a personal wrapper for Reacts cloneElement().

Button click won't update state in my React app

A button click shall filter my job-card array to only one category. E.g. button "Marketing" should filter to those jobs from array who have prop "jobstags: Marketing". I used a very similar procedure like for my input which filters jobs perfectly.
I can console log my event (the button click) with the according value ("Marketing"). But it still doesn't filter correctly...
In my app I did this:
export default class App extends Component {
state = {
jobs: jobs,
searchfield: '',
jobtags: ''
}
onSearchChange = event => {
this.setState({ searchfield: event.target.value })
}
onClickChange = event => {
console.log(event.target.value)
this.setState({ jobtags: event.target.value })
}
render() {
const filteredJobs = this.state.jobs.filter(job => {
return (
job.position
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.company
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.jobtags.toLowerCase().includes(this.state.jobtags.toLowerCase())
)
})
// this.save()
if (this.state.jobs.length === 0) {
return <Loading>Loading...</Loading>
} else {
return (
<Router>
<React.Fragment>
<Route
exact
path="/"
render={() => (
<Home
jobs={filteredJobs}
searchChange={this.onSearchChange}
clickChange={this.onClickChange}
/>
)}
/>
onClickChange is what should update the state of tags
In my Home component I then simply pass the value on to the Categories component:
<Categories clickChange={clickChange} />
Finally it arrives in my Categories component where I say:
export default class Categories extends Component {
render() {
const { clickChange } = this.props
return (
<Wrapper>
<button value="Marketing" onClick={clickChange}>
<img
alt="Button"
src={require('/Users/markus/Documents/q4-2018/jobs-app/src/img/computer.png')}
/>
Frontend
</button> ...
Any ideas? Thx!
maybe you have to bind the "this" of "onClickChange", for example in the constructor of your App class.
Example :
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
jobs: jobs,
searchfield: '',
jobtags: ''
};
this.onClickChange = this.onClickChange.bind(this);
and it will work I think
You will have to bind it. Add this line to your constructor:
this.onClickChange = this.onClickChange.bind(this);

React router problem fetching data when needed

I have a tricky situation with react router 4.
Imagine I have a route
<Route path='/search/:term?' render={(props) => {
return (<ProductList
setFlag={(flag)=>this.setState({flag})}
{...props}
/>)
}} />
Now you can see I am using render in Route which means it will not unmount this component at each render rather update the old instance with new props.
However, at some point inside the ProductList the user calls setFlag function which you can see updates some property in parent.
Because of this, a rerender of the parent is caused. Which also calls componentWillReceiveProps(CWRP) of ProductList. Inside CWRP of ProductList I am always (unconditionally) fetching items with new props.
This causes my problem. You can see that when user updated flag, there was no need to fetch data again in CWRP, because updating that flag wasn't related to my data.
You could say that I should put some condition in CWRP that would do some check and fetch data only when it is necessary. However, I find it impossible to come up with such check. Because for example, ProductList receives a search term. I could, for example, compare a search term from the previous render to search term of new render and if they are different then to fetch data, however, that is incorrect, because even in case of same search term a fetch should be issued (maybe the data was updated on a server).
What solution do you see in such a situation?
So that my product list doesn't fetch data everytime the flag of parent changes?
Elevate your state and move your logic out of the render method and into a parent container-component, then utilize this.setState() to stop state updates OR use shouldComponentUpdate() to continue to allow state updates BUT stop re-renders when the flag hasn't been changed (either one will prevent ProductList from being updated):
import React, { Component } from 'react';
import ProductList from './ProductList';
export default class SearchTerms extends Component {
state = { flag: '' };
shouldComponentUpdate = (nextProps, nextState) => ( this.state.flag !== nextState.flag )
handleFlag = flag => this.setState(prevState => { return this.state.flag !== flag ? { flag } : null })
render = () => ( <ProductList setFlag={this.handleFlag} {...this.state} {...this.props} /> )
}
Then the route will change to:
<Route path='/search/:term?' component={SearchTerms} />
In addition, I'd avoid using componentWillReceiveProps() altogether and instead use componentDidUpdate().
Here's an example of a parent container-component controlling several component children. The children can update the parent via a passed down parent method.
In this simple example, searchForPlayer's onChange and onSubmit updates the parent's searchTerm state and changes the URL query via parent's handleSubmit method. The URL query change triggers the parent's componentDidUpdate method, which then fetches new data and updates the displayPlayerList component.
URL before:
/players/all
URL after form submit:
/players/player?number=${this.state.searchTerm}
So if a user types out the URL to:
/players/player?number=10
or
/players/fdskmsdfk?number=10
and hits enter, it'll load a filtered list because it's only looking for a number query.
If they go to:
/player/dsfdsdfdsdf
or
player/1223345
or anything without a number query, then it'll just fetch all the players instead (this can be handled differently, but was done for simplicity).
Working example: https://codesandbox.io/s/xn3p3o6vq
containers/PlayersList.js (parent container-component)
import isEmpty from "lodash/isEmpty";
import React, { Component, Fragment } from "react";
import qs from "qs";
import DisplayPlayerList from "../components/displayPlayerList";
import NoPlayerFound from "../components/noPlayerFound";
import SearchForPlayer from "../components/searchForPlayer";
import ServerError from "../components/serverError";
import Spinner from "../components/spinner";
export default class PlayersList extends Component {
state = {
err: "",
isLoading: true,
searchTerm: "",
players: [],
noplayer: "",
number: ""
};
componentDidMount = () => this.fetchPlayers();
componentDidUpdate = (prevProps, prevState) => this.props.location.search !== prevProps.location.search && this.fetchPlayers();
fetchPlayers = () => {
const { number } = qs.parse(this.props.location.search, { ignoreQueryPrefix: true })
fetch(`https://jsonplaceholder.typicode.com/users${number ? `/${number}` : ""}`)
.then(response => response.json())
.then(players =>
this.setState({
err: "",
players: !number ? [...players] : [players],
noplayer: isEmpty(players) ? true : false,
isLoading: false,
number,
searchTerm: ""
})
)
.catch(err => this.setState({ err: err.toString() }));
};
handleChange = e => this.setState({ searchTerm: e.target.value });
handleSubmit = e => {
e.preventDefault();
this.props.history.push(`/players/player?number=${this.state.searchTerm}`);
};
render = () => (
this.state.isLoading // (if isLoading is true..)
? <Spinner /> // (then show a spinner)
: <div style={{ padding: 20, width: 500 }}> // (otherwise...)
<SearchForPlayer // (show player search form and...)
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
{...this.state}
/>
{ this.state.err // (if there's an error...)
? <ServerError {...this.state} /> // (show the error)
: this.state.noplayer // (otherwise, if there's no player...)
? <NoPlayerFound {...this.state} /> // (show no player found)
: <DisplayPlayerList {...this.state} /> // (otherwise, display updated list)
}
</div>
);
}
components/searchForPlayer.js (child component)
import React from "react";
export default ({ handleChange, handleSubmit, searchTerm }) => (
<form onSubmit={handleSubmit}>
<input
className="uk-input"
type="number"
value={searchTerm}
onChange={handleChange}
placeholder="Search for player by number..."
style={{ width: 300, marginRight: 10 }}
min={1}
/>
<button
disabled={!searchTerm}
className="uk-button uk-button-primary"
type="submit"
>
Search
</button>
</form>
);
components/displayPlayerList.js (child component)
import map from "lodash/map";
import React from "react";
export default ({ players }) => (
<ul style={{ listStyleType: "none" }}>
{map(players, ({ id, name, username, email }) => (
<li style={{ margin: "10px 0" }} key={id}>
<strong>Player # {id}</strong>
<span> - {name}</span>
</li>
))}
</ul>
);
components/noPlayerFound.js (child component)
import React from "react";
export default ({ number }) => (
<div style={{ color: "red", padding: 20 }}>
No player was found matching #{number}!
</div>
);
component/serverError.js (child component)
import React from "react";
export default ({ err }) => (
<div style={{ color: "red", padding: 20 }}>
<i style={{ marginRight: 5 }} className="fas fa-exclamation-circle" /> {err}
</div>
);

React integrate component through functions ( HOC )

I am building an admin template using reactJs and one of the components that make up the entire page looks like this.
class UserManagement extends React.Component {
state = {
showDeleteModal: false
};
_toggleDeleteModal = () => {
this.setState(prevState => ({
showDeleteModal: !prevState.showDeleteModal
}));
};
onDeleteRow = () => {
console.log("delete");
};
render() {
return (
<div>
{this.state.showDeleteModal && (
<DeleteModal
title="Delete Data ?"
description="Are you sure you want to delete this data from the system ?"
onDeleteAction={this.onDeleteRow}
onToggleModal={this._toggleDeleteModal}
/>
)}
</div>
);
}
}
DeleteModal is basically a modal that pops up and displays a bunch of options to the user based on which the user selects an option, this is one of the many modals that are contained in this UserManagement component. As you can see I need to write out the DeleteModal code in the render function, doing this for the other modals causes excess code on this page that can probably be extracted out somehow.
In the end I would like to be able to do something like th
I didn't get your question clearly but am hoping you are asking how you could extract out the DeleteModal component. That being said, here is my thought;
class UserManagement extends React.Component {
state = {
showDeleteModal: false
};
_toggleDeleteModal = () => {
this.setState(prevState => ({
showDeleteModal: !prevState.showDeleteModal
}));
};
onDeleteRow = () => {
console.log("delete");
};
renderDeleteModal = () => (
<DeleteModal
title={"Delete Data ?"}
description={
"Are you sure you want to delete this data from the system ?"
}
onDeleteAction={this.onDeleteRow}
onToggleModal={this._toggleDeleteModal}
/>
);
render() {
return (
<React.Fragment>
{this.state.showDeleteModal && this.renderDeleteModal}
</React.Fragment>
);
}
}
I'm making the assumption that all the modals that you have are having similar structure, and since at any point of time only one modal will be shown to the user
you can create on reusable modal that has the following fields:
Title
Description
Action button
Cancel button
You can try creating something like this:
class UserManagement extends React.Component {
constructor(props) {
this.state = {
showModal: false,
modalTitle: "",
modalDescription: "",
modalAction: null
}
}
showDeleteModal() {
this.setState(prevState => ({
modalTitle: "Delete Data ?",
modalDescription: "Are you sure you want to delete this data from the system ?",
modalAction: this.onDeleteRow
}), this._toggleDeleteModal)
}
_toggleDeleteModal = () => {
this.setState(prevState => ({
showModal: !prevState.showModal
}))
};
onDeleteRow = () => {
console.log("delete");
};
render() {
return (
<div>
{this.state.showModal && (
<Modal
data={this.state.modal}
onToggleModal={this._toggleModal}
/>
)}
</div>
);
}
}
You can have one specific function for each of your use case (like Delete) which sets that title, description etc.
You can further move all the code that I've shown to a HOC and import it in your UserManagement component, if you think they will perform static operations with no input requirement from UserManagement component.

Updating state in react js

I have this code that renders a dropdown component from material ui and it's populated with data coming from a WS.
I set an initial value that is the firs element coming from the WS so when I render the page for the first time I can see the correct value in the dropdown.
My issue is when I try to select a different value on the dropdown, I'm not able to do it and I think is because I'm not updating the state, I have a method called "handleChange" but I'm missing something there but don't know what.
This is the code and hope someone can help with this, I'm new to react and still to practice much more.
import React, { Component } from 'react';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
export default class WebserviceTest extends Component {
constructor() {
super();
this.state = {
data: [],
selected: ''
};
this.renderOptions = this.renderOptions.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
const url = 'https://randomuser.me/api/?results=4';
fetch(url)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
data: findResponse.results,
selected: findResponse.results[0].name.first
});
console.log('----- ', this.setState.selected);
});
}
handleChange(value) {
this.setState({ selected: (value) });
}
renderOptions() {
return this.state.data.map((dt, i) => {
return (
<MenuItem
key={i}
value={dt.name.first}
primaryText={dt.name.first} />
);
});
}
render() {
return (
<div>
<DropDownMenu value={this.state.selected} onChange={this.handleChange}>
{this.renderOptions()}
</DropDownMenu>
</div>
);
}
}
Any help will be very welcome!
Thanks in advance..
In material UI dropdown, the selected value appears as third argument. So use something like this for your handleChange method
handleChange(event, index, value) {
this.setState({ selected: (value) });
}
Ref: http://www.material-ui.com/#/components/dropdown-menu#properties

Categories

Resources