I have a component that won't render it's sub-component. There's no errors in the console. I get the data I need from the web call, no errors with that. Not sure why the Project component isn't rendering anything.
Data Retrieval functions in separate file:
window.getCurrentUsersGroups = function() {
var d = $.Deferred();
var currentUsersBusinessArea = null;
var userGroups = $().SPServices({
operation: "GetGroupCollectionFromUser",
userLoginName: $().SPServices.SPGetCurrentUser()
});
userGroups.then(function(response) {
var groups = [];
$(response).find("Group").each(function() {
var self = $(this);
groups.push(self.attr("Name"))
});
currentUsersBusinessArea = _.filter(groups, function(group) {
return _.startsWith(group, "BusinessArea")
});
d.resolve(getListings(currentUsersBusinessArea[0]))
})
return d.promise();
}
window.getListings = function(businessArea) {
var d = $.Deferred();
var projects = [];
var listings = $().SPServices.SPGetListItemsJson({
listName: "Projects",
CAMLQuery: "<Query><Where><Eq><FieldRef Name='" + businessArea + "'/><Value Type='String'>Unassigned</Value></Eq></Where></Query>"
});
listings.then(function() {
var result = this.data;
result.map(function(project){
projects.push({
id: project.ID,
pID: project.ProjectID,
title: project.Title,
status: project.Status,
created: project.Created,
businessArea: project.BusinessAreaFinanceAccounting,
sponsor: project.SponsoringArea,
comments: project.Comments
})
})
d.resolve({businessArea: businessArea, projects: projects})
})
return d.promise();
}
Listing Component:
class Listings extends React.Component {
constructor(props) {
super(props);
this.state = {
businessArea: null,
projects: [],
};
};
componentDidMount() {
let that = this;
window.getCurrentUsersGroups().then(function(response) {
response.then(function(data){
that.setState({businessArea: data.businessArea})
that.setState({projects: data.projects})
})
})
};
render() {
let {businessArea, projects} = this.state;
console.log(this.state)
return (
<section className="listingsContainer">
<h3>{businessArea}</h3>
<hr></hr>
<table className="ms-Table">
<thead>
<tr>
<th>Edit</th>
<th>Project ID</th>
<th>Project Name</th>
<th>Response Status</th>
<th>Initiated Date</th>
<th>BA Impact</th>
<th>Sponsor</th>
<th>Comments</th>
</tr>
</thead>
<tbody>
{
projects.map( function({project,index}) {
console.log(project.ID)
return <Project key={project.id} project={project} index={index} />
})
}
</tbody>
</table>
</section>
)
}
}
Project Component:
const Project = ({project, index}) => {
return (
<tr key={index + project.ID}>
<td>
<a href={_spPageContextInfo.webAbsoluteUrl + '/SitePages/Business%20Project%20Edit.aspx?ProjectID=' + project.ID}>
<span style="font-size:1em;" className="ms-Icon ms-Icon--editBox"></span>
</a>
</td>
<td>{project.ProjectID}</td>
<td>{project.Title}</td>
<td>{project.Status}</td>
<td>{project.Created}</td>
<td>{project.BusinessAreaFinanceAccounting}</td>
<td>{project.SponsoringArea}</td>
<td>{project.Comments}</td>
</tr>
);
};
Browser Result:
If I output $r in the console, the Listing component state has projects in it. But the react dev tool says the array is 0 and nothing is rendering. Confused.
Seems like you forgot to wrap your project with curly braces:
const Project = ({project}) => {
return (
<tr>
<td>
<a href={_spPageContextInfo.webAbsoluteUrl + '/SitePages/Business%20Project%20Edit.aspx?ProjectID=' + project}>
<span style="font-size:1em;" className="ms-Icon ms-Icon--editBox"></span>
</a>
</td>
<td>{project.ProjectID}</td>
<td>{project.Title}</td>
<td>{project.Status}</td>
<td>{project.Created}</td>
<td>{project.BusinessAreaFinanceAccounting}</td>
<td>{project.SponsoringArea}</td>
<td>{project.Comments}</td>
</tr>
);
};
Here's a nicer way to handle the Component's:
class Listings extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
businessArea: null
};
}
componentDidMount() {
let that = this;
let groups = [];
let userGroups = $().SPServices({
operation: "GetGroupCollectionFromUser",
userLoginName: $().SPServices.SPGetCurrentUser()
});
userGroups.then((response) => {
$(response).find("Group").each(function() {
let self = $(this);
groups.push(self.attr("Name"))
});
let currentUsersBusinessArea = _.filter(groups, (group) => _.startsWith(group, "BusinessArea"));
this.setState({businessArea: currentUsersBusinessArea})
}).then(getListings)
function getListings() {
let listings = $().SPServices.SPGetListItemsJson({
listName: "Projects",
CAMLQuery: "<Query><Where><Eq><FieldRef Name='" + that.state.businessArea + "'/><Value Type='String'>Unassigned</Value></Eq></Where></Query>"
});
listings.then(function() {
that.setState({data: this.data});
})
};
}
render() {
let {data, businessArea} = this.state;
return (
<section className="listingsContainer">
<h3>{`Business Area ${businessArea}`}</h3>
<hr></hr>
<table className="ms-Table">
<thead>
<tr>
<th>Edit</th>
<th>Project ID</th>
<th>Project Name</th>
<th>Response Status</th>
<th>Initiated Date</th>
<th>BA Impact</th>
<th>Sponsor</th>
<th>Comments</th>
</tr>
</thead>
<tbody>
{data.map({project,index}) => <Project key={index} project={project} /> }
</tbody>
</table>
</section>
)
}
}
const Project = ({project}) => (
<tr>
<td>
<a href={_spPageContextInfo.webAbsoluteUrl`/SitePages/Business%20Project%20Edit.aspx?ProjectID=${project}`}>
<span style={{'fontSize':'1em'}} className="ms-Icon ms-Icon--editBox"></span>
</a>
</td>
<td>{project.ProjectID}</td>
<td>{project.Title}</td>
<td>{project.Status}</td>
<td>{project.Created}</td>
<td>{project.BusinessAreaFinanceAccounting}</td>
<td>{project.SponsoringArea}</td>
<td>{project.Comments}</td>
</tr>
);
Tip: Never Mix Jquery with React Components
The main reason it wasn't rendering is because of this table cell:
<td>
<a href={_spPageContextInfo.webAbsoluteUrl + '/SitePages/Business%20Project%20Edit.aspx?ProjectID=' + project.ID}>
<span style="font-size:1em;" className="ms-Icon ms-Icon--editBox"></span>
</a>
</td>
The span tag as a style property. Which is preventing it from rendering from what I can see.
Related
My main component
Here I'm fetching data from backend and receiving it well. Here how it looks like.
And now I want to sort them by their properties like step 1, step 2. I'm using React query to fetch data but I'm not sure how to sort it. Also, I already have sorting functions. But, I don't know how to change data based on the sorting atribute.
.
import React, { useEffect, useState } from "react";
import useFetchTable from "../../../../api/table/useFetchTable";
const TableList = () => {
const { data: response, status, isLoading } = useFetchTable();
// const [sortField, setSortField] = useState("");
// const [order, setOrder] = useState("asc");
// const handleSortingChange = (accessor) => {
// const sortOrder =
// accessor === sortField && order === "desc" ? "asc" : "desc";
// setSortField(accessor);
// setOrder(sortOrder);
// handleSorting(accessor, sortOrder);
// };
// const handleSorting = (sortField, sortOrder) => {
// if (sortField) {
// const sorted = [...data].sort((a, b) => {
// if (a[sortField] === null) return 1;
// if (b[sortField] === null) return -1;
// if (a[sortField] === null && b[sortField] === null) return 0;
// return (
// a[sortField].toString().localeCompare(b[sortField].toString(), "en", {
// numeric: true,
// }) * (sortOrder === "asc" ? 1 : -1)
// );
// });
// setData(sorted);
// }
// };
if (status === "error") {
return "Error";
}
if (isLoading) {
return "Loading...";
}
console.log(response);
const Print = ({ children }) => {
return (
<span className="text-xs bg-blue-100 rounded-full px-2 py-0.5 ml-2">
{children}%
</span>
);
};
return (
<div>
<table>
<thead className="border-b-2">
<tr>
<th className="py-1">Product Name</th>
<th>Purchases</th>
<th>US</th>
<th>Ch Step 1</th>
<th>Ch Step 2</th>
<th>CVR</th>
<th> 1</th>
<th>Upsell 2</th>
<th>Upsell 3</th>
</tr>
</thead>
<tbody>
{response.data?.map((row, idx) => (
<tr key={idx}>
<td>{row.name}</td>
<td>
{row.purchases[0]} <Print>{row.purchases[1]}</Print>
</td>
<td>
{row.unique_sessions} <Print>100</Print>
</td>
<td>
{row.checkout_step_1[0]} <Print>{row.checkout_step_1[1]}</Print>
</td>
<td>
{row.checkout_step_2[0]} <Print>{row.checkout_step_2[1]}</Print>
</td>
<td>
<Print>{`${row["cvr_%"]}`}</Print>
</td>
<td>
{row.upsell_1_takes[0]} <Print>{row.upsell_1_takes[1]}</Print>
</td>
<td>
{row.upsell_2_takes[0]} <Print>{row.upsell_2_takes[1]}</Print>
</td>
<td>
{row.upsell_3_takes[0]} <Print>{row.upsell_3_takes[1]}</Print>
</td>
</tr>
))}
</tbody>
</table>
TableList
{/* {data?.map((el) => {
el.title;
})} */}
</div>
);
};
export default TableList;
So for sorting based on your column header you can create a function to handle that onClick of the particular header. Like in the below code I have used the firstName column for sorting. On clicking the first name header it will trigger the function sortByFirstName and added the sort functionality in it and updated the state of the setTableData . Hope this helps.
import React, { useEffect, useState } from 'react'
import { useQuery } from 'react-query'
import './style.css'
function Example () {
const [sorted, setSorted] = useState({ sorted: "fname", reversed: false });
const [tableData, setTableData] = useState([])
const { data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://dummyjson.com/users?limit=10').then(
(res) => res.json(),
),
})
useEffect(() => {
if (data) {
setTableData(data?.users)
}
}, [data])
const sortByFirstName = () => {
setSorted({ sorted: "fname", reversed: !sorted.reversed })
const tableDataCopy = [...tableData];
tableDataCopy.sort((a, b) => {
let fnameA = a.firstName.toLowerCase();
let fnameB = b.firstName.toLowerCase();
if (sorted.reversed) {
return fnameB.localeCompare(fnameA)
}
return fnameA.localeCompare(fnameB)
})
setTableData(tableDataCopy)
}
return (
<div className='h-full w-full'>
<table className='data' cellspacing="0" cellpadding="0">
<thead>
<tr>
<th onClick={ sortByFirstName }>First Name</th>
<th >Last Name</th>
<th >Gender</th>
<th >Email</th>
<th >Bloodgroup</th>
<th >Age</th>
<th > Weight</th>
<th >Maiden Name</th>
<th >Phone</th>
</tr>
</thead>
<tbody>
{ tableData?.map((row, idx) => (
<tr key={ idx }>
<td>{ row.firstName }</td>
<td>
{ row.lastName }
</td>
<td>
{ row.gender }
</td>
<td>
{ row.email }
</td>
<td>
{ row.bloodGroup }
</td>
<td>
{ row.age }
</td>
<td>
{ row.weight }
</td>
<td>
{ row.maidenName }
</td>
<td>
{ row.phone }
</td>
</tr>
)) }
</tbody>
</table>
</div>
)
}
export default Example
The code that I posted below is the API request from which I make a table. This table has 4 columns: id, userid, title. I want to understand how I can sort by userid and title, as shown in the photo. It would be great if the steps were described in detail.
I'm trying to group the tab as shown in the photo, but I can't.
Can you suggest/show me how to do this?
Also wanted to know how to reset the group value of a column?
I will be grateful for any help.
My code:
import React from "react";
import "./GroupByUserID.css";
import { Link } from "react-router-dom";
export default class GroupByUserID extends React.Component {
// Constructor
constructor(props) {
super(props);
this.state = {
items: [],
};
}
componentDidMount = () => {
this.apiFetch();
};
//Fetch data from API
apiFetch = () => {
return fetch("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.json())
.then((json) => {
this.setState((prevState) => {
return { ...prevState, items: json };
});
});
};
// Sort UserID
setSortedItemsUserID = () => {
const { items } = this.state;
const sortedUserID = items.sort((a, b) => {
if (a.userId < b.userId) {
return items.direction === "ascending" ? -1 : 1;
}
if (a.userId > b.userId) {
return items.direction === "ascending" ? 1 : -1;
}
return 0;
});
console.log(sortedUserID);
this.setState((prevState) => {
return { ...prevState, items: sortedUserID };
});
};
render() {
const { items } = this.state;
return (
<div>
<h1>Home Page</h1>
<table>
<thead>
<tr>
<th>
<Link target="self" to="/">
View Normal
</Link>
</th>
<th>Group By UserID</th>
</tr>
</thead>
<thead>
<tr>
<th>
User ID
<button
type="button"
onClick={() => this.setSortedItemsUserID()}
>
⬇️
</button>
</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{items.map((item) => (
<tr key={item.userId + item.title}>
<td>{item.userId}</td>
<td>{item.title}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
I'm trying to create a CRUD application in react, and I have encountered several problems.
How can I remove an item from the table?
Is it possible to pass id to Info component in render method? How can I later link it to the element?
Why does the e.PreventDefault () method cause an error when I try to delete?
import React, { Component } from 'react';
const Info = ({ index, login, pass }) => (
<>
<thead>
<tr>
<th>ID</th>
<th>LOGIN</th>
<th>PASSWORD</th>
</tr>
</thead>
<tbody>
<tr>
<td key={index}>{index}{alert(index)}</td>
<td>{login}</td>
<td>{pass}</td>
</tr>
</tbody>
<input type="submit" value='X' onClick={() => this.del(index)}></input>
</>
);
class List extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
login: "",
pass: "",
};
this.add = this.add.bind(this);
this.show = this.show.bind(this);
this.del = this.show.bind(this);
}
add(e) {
e.preventDefault();
this.setState({
[e.target.name]: e.target.value,
});
}
show(e) {
e.preventDefault();
if (!this.state.login.length || !this.state.pass.length) {
return;
}
else {
const newUser = {
login: this.state.login,
pass: this.state.pass,
};
this.setState(state => ({
data: state.data.concat(newUser),
}))
}
}
del(index) {
const tab = this.state.data.filter((temp) => temp.index !== index);
this.setState(({
data: tab
}));
}
render() {
return (
<div>
<form onSubmit={this.show}>
<label>Login</label><br></br><input type='text' name='login' onChange={e => this.add(e)}></input><br></br>
<label>Password</label><br></br><input type='text' name='pass' onChange={e => this.add(e)}></input><br></br>
<input type="submit" value="Add"></input>
</form>
<table>
{this.state.data.map((val, index) => (
<>
<thead>
<tr>
<th>ID</th>
<th>LOGIN</th>
<th>PASSWORD</th>
</tr>
</thead>
<tbody>
<tr>
<td key={index}>{index}</td>
<td>{val.login}</td>
<td>{val.pass}</td>
</tr>
</tbody>
<input type="submit" value='X' onClick={() => this.del(index)}></input>
</>
))}
</table>
</div>
)
}
}
export default List;
There are small corrections in your code.
this.del = this.show.bind(this); should be this.del = this.del.bind(this);
You are trying to remove the element from the state data using index (this.state.data.filter((temp) => temp.index !== index);) but the element inside the data doesn't have an index property.
In that case, you can use splice to delete the element from the data.
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
login: "",
pass: "",
};
this.add = this.add.bind(this);
this.show = this.show.bind(this);
this.del = this.del.bind(this);
}
add(e) {
e.preventDefault();
this.setState({
[e.target.name]: e.target.value,
});
}
show(e) {
e.preventDefault();
if (!this.state.login.length || !this.state.pass.length) {
return;
}
else {
const newUser = {
login: this.state.login,
pass: this.state.pass,
};
this.setState(state => ({
data: state.data.concat(newUser),
}))
}
}
del(index) {
let {data}=this.state
data.splice(index, 1);
this.setState(({
data
}));
}
render() {
return (
<div>
<form onSubmit={this.show}>
<label>Login</label><br></br><input type='text' name='login' onChange={e => this.add(e)}></input><br></br>
<label>Password</label><br></br><input type='text' name='pass' onChange={e => this.add(e)}></input><br></br>
<input type="submit" value="Add"></input>
</form>
<table>
{this.state.data.map((val, index) => (
<React.Fragment>
<thead>
<tr>
<th>ID</th>
<th>LOGIN</th>
<th>PASSWORD</th>
</tr>
</thead>
<tbody>
<tr>
<td key={index}>{index}</td>
<td>{val.login}</td>
<td>{val.pass}</td>
</tr>
</tbody>
<input type="submit" value='X' onClick={() => this.del(index)}></input>
</React.Fragment>
))}
</table>
</div>
)
}
}
ReactDOM.render(<List />, document.querySelector("#app"))
Here is the demo jsFiddle
Hope it helps :)
I was trying to rewrite the todo app using functional Components and I could not figure out a way to destructure and array of objects. Appreciate any help.
The Original Component:
class ProductTable extends React.Component {
render() {
let rows = [];
let lastCategory = null;
this.props.products.forEach(function(product) {
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
The Functional Component I am trying to write:
const ProductTable = ({products = []}) => {
let rows = [];
let lastCategory = null;
products.forEach(function (product) {
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
};
Can the FormalParameter be more restrictive?
const ProductTable = ({products = []}) => ...
I have a table where there is one input box in each row. There are 3 rows in total and i need to calculate the total from those three input boxes value. But the state of value is not updating. I only get the initial state of value. For example, there is a state object of agent, hotel, admin. If i initialize the agent value 10, i get 10 in input box but when i try to change the value i only get 10. The value does not gets updated.
Here is the code
const Tbody = ({ roles, states, onChange, onBlur }) => {
const row = roles.map((role, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{role.label}</td>
<td>
<TextFieldGroup
id="formControlsText"
type="number"
name={role.name}
value={states[role.name]}
onChange={event => onChange(event)}
onBlur={event => onBlur(event)}
error={states.errors[role.name]}
required
/>
</td>
</tr>
));
return <tbody>{row}</tbody>;
};
class Commission extends React.PureComponent {
state = {
agentCommission: 0,
hotelCommission: 0,
adminCommission: 0,
errors: {},
isSubmitted: false
};
handleChange = event => {
console.log(event.target);
const fieldName = event.target.name;
this.setState(
{ [event.target.name]: parseFloat(event.target.value) },
() => {
this.validateField([fieldName]);
}
);
};
handleBlur = event => {
const fieldName = event.target.name;
this.validateField([fieldName]);
};
validateField = validate => {
const errors = { ...this.state.errors };
let hasError = false;
validate.forEach(field => {
if (
parseFloat(this.state[field]) > 100 ||
parseFloat(this.state[field]) < 0
) {
hasError = true;
errors[field] = 'cannot be less than 0 and more than 100';
} else {
errors[field] = '';
}
});
this.setState({ errors });
return !hasError;
};
render() {
const { agentCommission, adminCommission, hotelcommission } = this.state;
const totalCommission = agentCommission + adminCommission + hotelcommission;
console.log('totalCommission', totalCommission);
return (
<div className="table-responsive">
<table className="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>S.N</th>
<th>Role</th>
<th>Commission</th>
</tr>
</thead>
<Tbody
roles={[
{ name: 'agentCommission', label: 'Agent' },
{ name: 'hotelCommission', label: 'Hotel Owner' },
{ name: 'adminCommission', label: 'Admin' }
]}
states={{ ...this.state }}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<tbody>
<tr>
<td>
<button
className="btn btn-primary"
onClick={this.handleSubmit}
disabled={totalCommission === 100 ? false : true}>
Save Changes
</button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
In ReactJS, when you extend a React component class, you must initialize the state in the constructor. Also, you need to call the parent class' constructor via super(props). This is the only way that the React library's class can get access to your state values, as well as provide access in methods such as setState()
https://codepen.io/julianfresco/pen/ybrZNe/
class Commission extends React.PureComponent {
constructor(props, context) {
super(props)
this.state = {
agentCommission: 0,
hotelCommission: 0,
adminCommission: 0,
errors: {},
isSubmitted: false
};
// method instance bindings
this.handleChange = this.handleChange.bind(this)
this.handleBlur = this.handleBlur.bind(this)
this.validateField = this.validateField.bind(this)
}
// ...
// you had 1 typo in the render function, hotelCommission wasn't camel case
render() {
const { agentCommission, adminCommission, hotelCommission } = this.state;
// ...
}
}
The issue is the Commission class, where you are not initializing the state.
Your code should be like the following:
const Tbody = ({ roles, states, onChange, onBlur }) => {
const row = roles.map((role, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{role.label}</td>
<td>
<input
id="formControlsText"
type="number"
name={role.name}
value={states[role.name]}
onChange={event => onChange(event)}
onBlur={event => onBlur(event)}
error={states.errors[role.name]}
required
/>
</td>
</tr>
));
return <tbody>{row}</tbody>;
};
class Commission extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
agentCommission: 0,
hotelCommission: 0,
adminCommission: 0,
errors: {},
isSubmitted: false
};
}
handleChange(event) {
console.log(event.target);
const fieldName = event.target.name;
this.setState(
{ [event.target.name]: parseFloat(event.target.value) },
() => {
this.validateField([fieldName]);
}
);
};
handleBlur(event) {
const fieldName = event.target.name;
this.validateField([fieldName]);
};
validateField(validate) {
const errors = { ...this.state.errors };
let hasError = false;
validate.forEach(field => {
if (
parseFloat(this.state[field]) > 100 ||
parseFloat(this.state[field]) < 0
) {
hasError = true;
errors[field] = 'cannot be less than 0 and more than 100';
} else {
errors[field] = '';
}
});
this.setState({ errors });
return !hasError;
};
render() {
const { agentCommission, adminCommission, hotelcommission } = this.state;
const totalCommission = agentCommission + adminCommission + hotelcommission;
console.log('totalCommission', totalCommission);
return (
<div className="table-responsive">
<table className="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>S.N</th>
<th>Role</th>
<th>Commission</th>
</tr>
</thead>
<Tbody
roles={[
{ name: 'agentCommission', label: 'Agent' },
{ name: 'hotelCommission', label: 'Hotel Owner' },
{ name: 'adminCommission', label: 'Admin' }
]}
states={{ ...this.state }}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<tbody>
<tr>
<td>
<button
className="btn btn-primary"
onClick={this.handleSubmit}
disabled={totalCommission === 100 ? false : true}>
Save Changes
</button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
Fiddle demo: https://codesandbox.io/s/KO3vDRGjR