React v4 routing for dynamically created components - javascript

I have a list of objects that I'm using to create ReportCard components. When the user clicks on one of these ReportCards, I would like it to route to a ReportDetail component, getting the ID property of the ReportCard passed via URL params. The problem is the Match.Params object being received by the ReportDetail component returns as params: {id: ":id"}. I think the way I'm creating these components is what is causing the problem.
I've tried changing how the ReportCard components are being generated. By changing the level of where as well as the tags.
Report Area components creates the ReportCard components based on the amount of report objects.
Each ReportCard component has a link tag.
All ReportCard components have 1 Route tag.
I would like to pass the report.id from ReportCard into the Route URL parameter for ReportDetail.
Appreciate any help, sorry for all the code.
Report List:
export const reports = [
{
id: 1,
name: "Report 1",
company: "Company, Inc",
description: "Charts"
},
{
id: 2,
name: "Report 2",
company: "Company, Inc",
description: "Charts 2"
},
{
id: 3,
name: "Report 3",
company: "Company, Inc",
description: "Charts 3"
},
{
id: 4,
name: "Report 4",
company: "Company, Inc",
description: "Charts 4"
},
{
id: 5,
name: "Report 5",
company: "Company, Inc",
description: "Charts 5"
},
{
id: 6,
name: "Report 6",
company: "Company, Inc",
description: "Charts 6"
},
]
Report Area:
interface DetailParams {
id: string;
}
interface DetailsProps {
required: string;
match ? : match <DetailParams> ;
}
export interface IAppState {
reports: any[];
}
export default class ReportArea extends React.Component <DetailsProps,
IAppState> {
constructor(props: DetailsProps & IAppState) {
super(props);
this.state = reports;
}
public render() {
var groupSize = 2;
var rows = this.state.reports.map(function(report) {
return <Link to = "/reports/:id"><ReportCard md={3}
key = {report.id}><ReportCard></Link > ;
}).reduce(function(row, element, index) {
index % groupSize === 0 && row.push([]);
row[row.length - 1].push(element);
return row;
}, []).map(function(rowContent, index) {
return <Row key = {index}> {rowContent}</Row>;
});
return <div className = "windowlayout"> {
rows}<Route path = '/reports/:id'
component = {ReportDetail}/> </div>;
}
}
ReportCard Component:
export default class ReportCard extends React.Component<DetailsProps,
any> {
props: any;
constructor(props: DetailsProps) {
super(props);
}
public render() {
const match = this.props.match
return (
<div>
<Card className="subPanel" key={this.props.id}>
<CardImg className="imagesec" src="https://via.placeholder.com/150"
alt="Card image cap" />
</Card>
</div>
);
}
}
ReportDetail Component:
interface DetailParams {
id: string;
}
interface DetailsProps {
required: string;
match?: match<DetailParams>;
}
export default class ReportDetail extends React.Component<DetailsProps,any>
{
props: any;
constructor(props: DetailsProps) {
super(props);
}
public render() {
const {match} = this.props;
return (
<div>
<h2>
{match.params.id}
</h2>
</div>
)
}
}

The issue is <Link to = "/reports/:id">. This syntax is what you want for the Route, but for the link you should have the actual id. Something like:
const path = "/reports/" + report.id;
return <Link to = {path}>...

Related

How to fetch data with same name but in different interface in angular

I have two interface, one is cropFilter which is for checkbox filter and second interface is holding my data called Crop.
let me share my code for better understanding.
1. crop.model.ts
export class Crop { // Interface 1
name: string;
district: string
subCategory: Subcategory[];
}
export class Subcategory {
id: number;
name: string;
}
export class CropFilter { // Interface 2
name: string
checked: boolean
}
2. cropFilter.ts
import { CropFilter } from "./crop.model";
export const CROPSFILTER: CropFilter[] = [
{
name: "Rice",
checked: false
}, {
name: "Wheat",
checked: false
}, {
name: "Barley",
checked: false
}
]
The above interface is for checkbox filtration.
3. crop.data.ts
import { Crop } from "./crop.model";
export const CROPS: Crop[] = [
{
name: "Rice",
district: "Thane",
subCategory: [
{
id: 1,
name: "Basmati",
},
{
id: 2,
name: "Ammamore",
}
]
},
{
name: "Rice",
district: "Nashik",
subCategory: [
{
id: 1,
name: "Basmati",
},
{
id: 2,
name: "Ammamore",
}
]
},
{
name: "Wheat",
district: "Nashik",
subCategory: [
{
id: 1,
name: "Durum",
},
{
id: 2,
name: "Emmer",
}
]
},
{
name: "Barley",
district: "Ratnagiri",
subCategory: [
{
id: 1,
name: "Hulless Barley",
},
{
id: 2,
name: "Barley Flakes",
}
]
},
{
name: "Barley",
district: "Thane",
subCategory: [
{
id: 1,
name: "Hulless Barley",
},
{
id: 2,
name: "Barley Flakes",
}
]
}
];
This is the actual data. All I want to fetch data from crop.data.ts based on crop.filter.ts
for better clearance let me show you the html part as well :
1. all-trade.html
<div class="container" *ngIf="crops$ | async">
<div *ngFor="let item of cropFilterCheckbox$ | async; let i = index">
<mat-checkbox [checked]="item.checked" (change)="onChange($event, i, item)">
{{ item.name }}
</mat-checkbox>
</div>
<br />
<h4>JSON data:</h4>
<pre>
{{ cropFilterCheckbox$ | async | json }}
<div *ngFor="let crop of cropFilterCheckbox$ | async"
[hidden]="!crop.checked"
>{{ crop.name }}
</div>
<button type="button" class="btn">Basic</button>
</pre>
</div>
2. crop.service.ts
import { Injectable } from "#angular/core";
import { Observable, of } from "rxjs";
import { Crop, CropFilter, DistrictFilter } from "../shared/crop.model";
import { CROPS } from "../shared/crop.data";
import { CROPSFILTER } from '../shared/cropFilter';
#Injectable({
providedIn: "root"
})
export class CropService {
constructor() { }
crops: Crop[] = CROPS;
cropFilterCheckbox: CropFilter[] = CROPSFILTER;
getAllCrops(): Observable<Crop[]> {
return of(this.crops);
}
getCropFilter(): Observable<CropFilter[]> {
return of(this.cropFilterCheckbox)
}
getCrop(name: string): Observable<any> {
const crop = this.crops.filter(crop => crop.name === name)[0];
return of(crop);
}
}
The final output looks like this :
Now please guide me how to fetch data from crop.data.ts based on crop.filter.ts
Like when user check Rice checkbox, its should fetch all the details of Rice present in crop.data.ts file and display on the screen.
On checkbox change write an event handle like below. Maintain which are the checkbox user has checked in a variable "AppliedFilter" and then pass that array list to your service method.
onChange(status, name) {
if (status && this.appliedFilter.indexOf(name) === -1) {
this.appliedFilter.push(name);
} else {
this.appliedFilter = this.appliedFilter.filter((x) => x !== name);
}
this.crops$ = this.cropService.getCrop(this.appliedFilter);
}
In your service method based on that array filter your records like below.
getCrop(names: string[]): Observable<any> {
const crop = this.crops.filter((crop) => names.includes(crop.name));
return of(crop);
}
Here is the working sandbox.
https://codesandbox.io/s/filter-data-x2p0w?file=/src/app/app.component.ts:289-294

Pass dynamic content to the react table sub component

I am using react table v6 for grid purposes. I am trying to implement a subcomponent where in the data to this sub component needs to be passed dynamically. This sub component should expand and collapse on click of arrows. I have tried the following , but the sub component is not rendering any data. I am creating a wrapper for this, the data to the subcomponent should be passed dynamically based on the source data.
https://codesandbox.io/s/react-table-row-table-subcompoentn-sk14i?file=/src/DataGrid.js
import * as React from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
export default class DataGrid extends React.Component {
renderSubComponent = original => {
console.log(original);
return (
original.nested &&
original.nested.map((i, key) => (
<React.Fragment key={key}>
<div>{i.name}</div>
<div>{i.value}</div>
</React.Fragment>
))
);
};
render() {
return (
<ReactTable
data={this.props.data}
columns={this.props.columns}
SubComponent={this.renderSubComponent}
/>
);
}
}
import * as React from "react";
import { render } from "react-dom";
import DataGrid from "./DataGrid";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
columns: [],
allData: []
};
}
componentDidMount() {
this.getData();
this.getColumns();
}
getData = () => {
const data = [
{
firstName: "Jack",
status: "Submitted",
nested: [
{
name: "test1",
value: "NA"
},
{
name: "test2",
value: "NA"
}
],
age: "14"
},
{
firstName: "Simon",
status: "Pending",
nested: [
{
name: "test3",
value: "NA"
},
{
name: "test4",
value: "Go"
}
],
age: "15"
}
];
this.setState({ data });
};
getColumns = () => {
const columns = [
{
Header: "First Name",
accessor: "firstName"
},
{
Header: "Status",
accessor: "status"
},
{
Header: "Age",
accessor: "age"
}
];
this.setState({ columns });
};
onClickRow = rowInfo => {
this.setState({ allData: rowInfo }, () => {
console.log(this.state.allData);
});
};
render() {
return (
<>
<DataGrid
data={this.state.data}
columns={this.state.columns}
rowClicked={this.onClickRow}
/>
</>
);
}
}
render(<App />, document.getElementById("root"));
try spread the argument and it will work :
...
renderSubComponent = ({original}) => {
...

React.js onChange handler changes all input field when typing.. How do I get each one to type when it is the event target?

I am working on an old app and I am trying to restructure the form that will be used to perform crud on my backend. I decided to make the input reusable, however, I have an issue that when I type into one box I type into all of them, that is, they are all the event target at once! how do I make it so only the input in focus will be typed into?
//update.js
import React, { Component } from "react";
import axios from "../../node_modules/axios";
import Input from "./input";
import "./update.css";
class Update extends Component {
constructor(props) {
super(props);
this.state = {
_id: "",
brand: "",
name: "",
price: "",
imageLink: "",
productLink: "",
category: "",
productType: "",
};
this.handleChange = this.handleChange.bind();
this.onSubmit = this.onSubmit.bind();
}
handleChange = (evt) => {
this.setState({ [evt.target.name]: evt.target.value });
};
onSubmit = (evt) => {
evt.preventDefault();
console.log(this.state);
axios
.put(
`https://makeupinfo.herokuapp.com/product${this.state._id}`,
this.state
)
.then((res) => {
console.log(res);
})
.then((err) => {
console.log(err);
});
};
render() {
const {
_id,
brand,
name,
price,
imageLink,
productLink,
productCategory,
productType,
} = this.state;
const info = [
{ name: "_id", placeholder: "product ID", value: _id },
{ name: "brand", placeholder: "brand", value: brand },
{ name: "name", placeholder: "product name", value: name },
{ name: "price", placeholder: "price", value: price },
{ name: "image-link", placeholder: "image link", value: imageLink },
{ name: "product-link", placeholder: "product link", value: productLink },
{
name: "product category",
placeholder: "product category",
value: productCategory,
},
{ name: "product type", placeholder: "product type", value: productType },
];
return (
<div>
<h2 className='links'>Search by ID and update</h2>
<div className='form-container'>
<Input props={info}></Input>
</div>
</div>
);
}
}
export default Update;
and the child component:
import React from "react";
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (e) => {
e.preventDefault();
this.setState({ value: e.target.value });
console.log(this.state.value);
};
render() {
const data = this.props.props;
const dataArray = data.map((item) => (
<input
className='form-control form-control-sm'
name={item.name}
type='text'
placeholder={item.placeholder}
value={this.state.value}
onChange={this.handleChange}
key={item.name}
/>
));
return <div>{dataArray}</div>;
}
}
export default Input;
All your inputs share the same state.value from the Input component. So when you edit one, you edit all of them. Your Input component should only render one input, and you should move info.map to the Update component.
Right now you call map inside Input which generate all the inputs with one shared value. If you do the map in your Update component, you will render one input for each Input, and they will all have their own value, so no more side effect.

React: Getting runtime error/warning in browser when a component is rendered

I am trying to create the left hand menu like this image for my test application using react, and I am getting the following warning/error at runtime on the browser console when loaded.
index.js:1375 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `LeftNav`. See fb.me/react-warning-keys for more information.
in Fragment (at LeftNav.js:12)
in LeftNav (at App.js:131)
in div (at App.js:120)
in div (at App.js:118)
in div (at App.js:116)
in Router (created by BrowserRouter)
in BrowserRouter (at App.js:115)
in App (at src/index.js:7)
Each of the navItems props items have distinct IDs, so every component should already have unique keys?
Should the React.Fragment have a unique key as well?
The LeftNav component is as below
class LeftNav extends Component {
render() {
return (
<ul>
{this.props.navItems.map((navItem) => {
return (
<React.Fragment>
<LeftNavItem key={navItem.id} navItem={navItem} menuLevel={0} />
{navItem.subMenu.map((subMenuItem) => {
return <LeftNavItem key={subMenuItem.id} navItem={subMenuItem} menuLevel={1} />
})}
</React.Fragment>
)
})}
</ul>
)
}
}
LeftNav.propTypes = {
navItems: PropTypes.array.isRequired
}
export default LeftNav;
LeftNavItem component is as below.
import React, { Component } from 'react'
import PropTypes from 'prop-types';
class LeftNavItem extends Component {
getClassName = () => {
if(this.props.menuLevel === 0) {
return 'parent_wrapper';
} else {
return '';
}
}
render() {
return (
<li className={this.getClassName()}>
{this.props.navItem.title}
</li>
)
}
}
LeftNavItem.propTypes = {
navItem: PropTypes.object.isRequired
}
export default LeftNavItem;
The props for LeftNav class is
leftNav: [
{
id: 1,
title: 'System Admin',
active: false,
subMenu: [
{
id: 2,
title: '',
active: false
},
{
id: 3,
title: '',
active: false
},
{
id: 4,
title: '',
active: false
},
{
id: 5,
title: '',
active: false
},
{
id: 6,
title: '',
active: false
},
{
id: 7,
title: '',
active: false
},
{
id: 8,
title: '',
active: false
},
{
id: 9,
title: '',
active: false
}
]
},
{
id: 10,
title: 'Reports',
active: false,
subMenu: [
{
id: 11,
title: 'Reports',
active: false
},
{
id: 12,
title: 'Dashboard',
active: true
},
{
id: 13,
title: 'Templates',
active: false
}
]
Thanks!!
Add your key to the fragment instead of the nested tag.
return (
<React.Fragment key={navItem.id}>
<LeftNavItem navItem={navItem} menuLevel={0} />
{navItem.subMenu.map((subMenuItem) => {
return <LeftNavItem key={subMenuItem.id} navItem={subMenuItem} menuLevel={1} />
})}
</React.Fragment>
)

.setState(): Filtering a person from an array of objects on button click

In the code below, I am trying to remove a person from what will eventually be an org chart when the delete button next to their name is clicked. At the moment, nothing happens. The closest I've come is all 5 people being deleted when any one of the delete buttons is clicked, but I only want the one person deleted who's button is clicked. I feel like I'm making more of a JS error than a React error.
See the full code sandbox here.
Any help would be appreciated, thank you!
import React from "react";
import { Component } from "react";
const list = [
{
name: "Person 1",
phone: "123-4567",
itemId: 11
},
{
name: "Person 2",
phone: "123-4567",
itemId: 12
},
{
name: "Person 3",
phone: "123-4567",
itemId: 23
},
{
name: "Person 4",
phone: "123-4567",
itemId: 34
},
{
name: "Person 5",
phone: "123-4567",
itemId: 45
}
];
class Entry extends Component {
constructor(props) {
super(props);
this.state = {
list: list
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
this.setState({
list: this.state.list.filter(function(person) {
return person !== e.target.value;
})
});
}
render() {
return (
<div>
{this.state.list.map(item =>
<tr key={item.itemId}>
<td>
{item.name}
</td>
<td>
{item.phone}
</td>
<td>
<a className="delete" onClick={this.handleClick} />
</td>
</tr>
)}
</div>
);
}
}
export default Entry;
Your click event has no value, you can pass the itemId:
onClick={() => this.handleClick(item.itemId)}
Then your handleClick should look like:
handleClick(itemId) {
this.setState({
list: this.state.list.filter(function(person) {
return person.itemId !== itemId;
})
});
}
https://codesandbox.io/s/mo2l8z7469
Both the above solution violates one of the best practices or I should say essential practices of react, that we should use property initializer syntax, which is passing the function defined above instead of passing an arrow function inside prop you can read it here in last paragraph of https://facebook.github.io/react/docs/handling-events.html
class Entry extends Component {
/* your handler method */
handleDelete(itemId){
return () => {
this.setState({
/* logic to filter deleted item from old state */
});
}
}
/* render method */
render() {
return (
<div>
{/* passing onDelete callback */}
<a className="delete" onClick={this.handleClick(item.id)} />
</div>
)
}
}
//import React from 'react';
//import ReactDOM from 'react-dom';
const list = [
{
name: "Person 1",
phone: "123-4567",
itemId: 11
},
{
name: "Person 2",
phone: "123-4567",
itemId: 12
},
{
name: "Person 3",
phone: "123-4567",
itemId: 23
},
{
name: "Person 4",
phone: "123-4567",
itemId: 34
},
{
name: "Person 5",
phone: "123-4567",
itemId: 45
}
];
class Entry extends React.Component {
constructor(props) {
super(props);
this.state = {
list: list
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(item) {
let filterList = this.state.list.filter((user) => {
if(user.itemId === item.itemId) {
return false;
}
return true;
})
this.setState({
list: filterList
});
}
render() {
return (
<div>
<table>
<tbody>
{this.state.list.map(item =>
<tr key={item.itemId}>
<td>
{item.name}
</td>
<td>
{item.phone}
</td>
<td>
<button className="delete" onClick={() => this.handleClick(item)} >Del</button>
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
ReactDOM.render(
<Entry />,
document.getElementById('app')
);
delete
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Categories