I have many pages in my app. How can I make another button and make it shorter?
Ex <<1,2,3,4,5,6,7,8,9,10 >> after I click >> it will become 11,12,13,14,15,16,17,18,19,20
this is my design
I hope can look like this
enter image description here
I only know how to do the basic one...
const indexOfLastTodo = this.state.currentPage * this.state.todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - this.state.todosPerPage;
const currentTodos = filteredDatas.slice(indexOfFirstTodo, indexOfLastTodo);
const renderTodos = currentTodos.map((todo, index) => {
return <SearchResults item={todo} testkey={index} account={this.state.accountData} key={index}></SearchResults>
});
Collapse
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(filteredDatas.length / this.state.todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<PaginationItem key={number}>
<PaginationLink key={number}
id={number}
onClick={this.handleClick} href=“#”>
{number}
</PaginationLink>
</PaginationItem >
);
});
<Pagination aria-label=“Page navigation example”>
{renderPageNumbers}
</Pagination>
Here my approach.
https://codesandbox.io/s/xpnp2k0214
Demo : https://xpnp2k0214.codesandbox.io/
Hope it will help u.
Edited
Explanation:
//Data source
const allUsers = [
{
id: "1130",
employee_name: "muaaa",
employee_salary: "123",
employee_age: "23",
profile_image: ""
}
.....
];
//Local state for users array and tableOptions object.
this.state = {
users: [],
isLoaded: false,
tableOptions: {
perpage: "10",
page: 1,
tot_count: 1
}
};
//Set the default page to 1 when the component mounted.
componentDidMount() {
//Update the size of allUsers array
let tableOptions = this.state.tableOptions;
tableOptions.tot_count = allUsers.length;
//Update the State
this.setState({ tableOptions }, () => {
this.setPage(1);
});
}
//Update the users array and current page
setPage = p => {
let tableOptions = this.state.tableOptions;
let page_size = parseInt(tableOptions.perpage);
//Finding the limits of allUsers array from current page
let from = (p - 1) * page_size;
let to = from + page_size;
//Slice the allUsers array with limit
let users = allUsers.slice(from, to);
tableOptions.page = p;
//Update the State
this.setState({ tableOptions, users, isLoaded: true });
};
//Generate the pagination from state.tableOptions
getPagination = () => {
let tOptions = this.state.tableOptions;
let per_page = tOptions.perpage;
let count = tOptions.tot_count;
let pages = count / per_page;
if (pages > 1) {
let pgarr = [];
pgarr[pages - 1] = 0;
var _pages = [];
let i = 0;
for (; i < pages; i++) {
_pages.push(i + 1);
}
let current_page = tOptions.page;
let new_pages = [];
if (_pages.length > 10) {
let start = 0,
end = 10;
let _end = _pages[_pages.length - 1];
let prev_page = parseInt(current_page - 5);
if (prev_page > 0) {
start = prev_page;
end = parseInt(current_page + 5);
end = end > _end ? _end : end;
}
for (i = start; i < end; i++) {
new_pages.push(i + 1);
}
new_pages[0] = _pages[0];
new_pages[new_pages.length - 1] = _pages[_pages.length - 1];
} else {
new_pages = _pages;
}
return (
<div className="row">
<div className="col" style={{ textAlign: "left" }}>
{new_pages.map((p, i) => (
<button
key={i}
style={
tOptions.page !== p
? { backgroundColor: "red" }
: { backgroundColor: "green" }
}
onClick={() => this.setPage(p)}
>
{p}
</button>
))}
</div>
</div>
);
} else {
return <div />;
}
};
//Finally the rendering the users table with pagination.
render() {
if (!this.state.isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className="App">
<div className="row" style={{ marginBottom: "15px" }}>
<div className="col">
<table border="1">
<thead>
<tr>
{Object.keys(this.state.users[0]).map((tr, i) => (
<td key={i}>{tr.split("_").join(" ")}</td>
))}
</tr>
</thead>
<tbody>
{this.state.users.map((user, i) => (
<tr key={i}>
<td>{user.id}</td>
<td>{user.employee_name}</td>
<td>{user.employee_salary}</td>
<td>{user.employee_age}</td>
<td>{user.profile_image}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{this.getPagination()}
</div>
);
}
Related
I am making a Sudoku Slover Application using react and express, my application solves the Sudoku for the Valid Sudoku but whenever I give same input in the same row. It doesn't show any error even though I have written in my code to display that error but its not being displayed can Anyone please tell me what's wrong in my Code.
Frontend Code - React
import "./App.css";
import { useState } from "react";
function App() {
const [solved, setSolved] = useState(false);
const [isSolved, setIsSolved] = useState(false);
const [data, setData] = useState("");
const [sudokuGrid, setSudokuGrid] = useState(
Array.from(new Array(9), (a, b) => Array.from(new Array(9), (a, b) => 0))
);
const clearGrid = () => {
setSudokuGrid(
Array.from(new Array(9), (a, b) => Array.from(new Array(9), (a, b) => 0))
);
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
document.getElementById(i + ":" + j).value = "";
}
}
setSolved(false);
setIsSolved(false);
};
const handleSetSudokuGrid = (row, col, val) => {
setSudokuGrid((prev) => {
prev[row][col] = +val;
return prev;
});
};
const inputChangeHandler = (e) => {
const _id = e.target.id.split(":");
if (e.target.value === "") {
handleSetSudokuGrid(+_id[0], +_id[1], "");
}
if (/^[0-9]+$/.test(e.target.value.trim())) {
const current = e.target.value.trim()[e.target.value.trim().length - 1];
handleSetSudokuGrid(+_id[0], +_id[1], current);
e.target.value = current === "0" ? "" : current;
} else {
handleSetSudokuGrid(+_id[0], +_id[1], "");
e.target.value = "";
}
};
const handleSolveButton = async (event) => {
const res = await (
await fetch("/solve?data=" + JSON.stringify(sudokuGrid), {
method: "POST",
})
).json();
console.log(res.status);
console.log("/solve?data=" + JSON.stringify(sudokuGrid));
if (res.status >= 400) {
console.log("hello");
setIsSolved(false);
setSolved(true);
setData(res.comment || res.error);
console.log(res.comment);
console.log(res.error);
} else {
setIsSolved(true);
setSolved(true);
setData(res.data);
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
handleSetSudokuGrid(i, j, res.data[i][j]);
document.getElementById(i + ":" + j).value =
res.data[i][j].toString();
}
}
}
};
return (
<div className="App">
<div className="sudoku">
<h2 className="heading">Sudoku Solver</h2>
<div className="grid-container">
<table id="grid">
<tbody>
{[0, 1, 2, 3, 4, 5, 6, 7, 8].map((row, rindex) => {
return (
<tr key={rindex} id={"row-" + rindex}>
{[0, 1, 2, 3, 4, 5, 6, 7, 8].map((col, cindex) => {
return (
<td key={cindex}>
<input
onChange={inputChangeHandler}
className="input-field"
id={rindex + ":" + cindex}
autoComplete={"off"}
/>
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<div id="grid-result">
{(!solved && !isSolved) ? (
<button class="arrowMove" onClick={handleSolveButton}>
<p class="arrowMoveFirstText">Solve Sudoku?</p>
<div class="icon arrow"></div>
<p class="arrowMoveSecondText">Click here!</p>
</button>
) :
(solved && !isSolved) ? (
<div>
<button class="arrowMove" onClick={handleSolveButton}>
<p class="arrowMoveFirstText">Solve Sudoku?</p>
<div class="icon arrow"></div>
<p class="arrowMoveSecondText">Click here!</p>
</button>
<p>{data}</p>
</div>
) :
(
<div className="success">
<p>Solved</p>
<button class="arrowMove" onClick={clearGrid} id="btn-success">
<p class="arrowMoveFirstText">Clear Sudoku?</p>
<div class="icon arrow"></div>
<p class="arrowMoveSecondText">Click here!</p>
</button>
</div>
)}
</div>
</div>
</div>
</div>
);
}
export default App;
Backend Code - Express.js
import express from "express";
import cors from "cors";
import { join, resolve } from "path"
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { config } from "dotenv"
config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PORT = process.env.PORT || 6969;
const app = express();
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
function isSafe(sudoku, row, col, val) {
for (let i = 0; i < 9; i++) {
if (sudoku[row][i] === val) {
return false;
}
if (sudoku[i][col] === val) {
return false;
}
if (
sudoku[3 * Math.floor(row / 3) + Math.floor(i / 3)][
3 * Math.floor(col / 3) + (i % 3)
] === val
) {
return false;
}
}
return true;
}
function sudokuSolver(sudoku) {
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
if (sudoku[row][col] === 0) {
for (let val = 1; val <= 9; val++) {
if (isSafe(sudoku, row, col, val)) {
sudoku[row][col] = val;
let isNextSolutionPossible = sudokuSolver(sudoku);
if (isNextSolutionPossible){
return true;
}
else{
sudoku[row][col] = 0;
}
}
}
return false;
}
}
}
return true;
}
app.post("/solve", async (req, res) => {
const sudoku = JSON.parse(req.query.data)
if(!sudoku) {
res.status(404)
res.send({
status: 400,
error: "The sudoku data not found",
})
return
}
var state = sudokuSolver(sudoku);
if(!state) {
console.log("REQUEST Sent??");
console.log(state);
res.status(400)
res.send({
status: 400,
error: "Sudoku Insolvable",
comment: "Enter valid sudoku."
})
return
}
res.status(200)
res.send({
status: 200,
data: sudoku,
isSolved: state
})
})
app.use(express.static(join(__dirname, "build")))
app.get("*",(req,res)=>{
res.send(resolve(__dirname, "build", "index.html"));
})
app.listen(PORT, () => {
console.log("Server is running on the port: "+PORT);
});
I'm using a Searchable Tree component in Ant Design. I would like to filter treeNodes on search. So that if a search value is found, a treeNode is shown. If not, it is hidden (i. e. filtered).
There is a filterTreeNode prop in API. Is it possible to filter treeNodes (hide unrelevant treeNodes from search) with this prop? If not, how can achieve the desired result?
Here is my code for filterTreeNode function:
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = node.key.indexOf(searchValue) !== -1 ? true : false
console.log(searchValue);
console.log(result);
return result;
};
I can see that the result (true or false) is logged in the console, but with the Tree itself nothing happens.
Here is a link to codesandbox and the full code for the component:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Tree, Input } from "antd";
import gData from "./gData.js";
const { Search } = Input;
const dataList = [];
const generateList = (data) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const SearchTree = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onChange = (e) => {
const { value } = e.target;
const expandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
if (value) {
setExpandedKeys(expandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
} else {
setExpandedKeys([]);
setSearchValue("");
setAutoExpandParent(false);
}
};
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = title.indexOf(searchValue) !== -1 ? true : false;
console.log(searchValue);
console.log(result);
return result;
};
const loop = (data) =>
data.map((item) => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key
};
});
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={onChange}
/>
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(gData)}
filterTreeNode={filterTreeNode}
/>
</div>
);
};
ReactDOM.render(<SearchTree />, document.getElementById("container"));
As mentioned in the API document, filterTreeNode will highlight tree node, and will not hide it.
filterTreeNode
Defines a function to filter (highlight) treeNodes. When the function returns true, the corresponding treeNode will be highlighted
If you want to hide tree node, you will have to manually filter it first before before passing it to Tree in loop function, something like:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";
import gData from "./gData.js";
const { Search } = Input;
const dataList = [];
const generateList = (data) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const SearchTree = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [treeData, setTreeData] = useState(gData);
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onChange = (e) => {
const value = e.target.value?.toLowerCase();
const expandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
if (value) {
const hasSearchTerm = (n) => n.toLowerCase().indexOf(value) !== -1;
const filterData = (arr) =>
arr?.filter(
(n) => hasSearchTerm(n.title) || filterData(n.children)?.length > 0
);
const filteredData = filterData(gData).map((n) => {
return {
...n,
children: filterData(n.children)
};
});
setTreeData(filteredData);
setExpandedKeys(expandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
} else {
setTreeData(gData);
setExpandedKeys([]);
setSearchValue("");
setAutoExpandParent(false);
}
};
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = title.indexOf(searchValue) !== -1 ? true : false;
console.log(searchValue);
console.log(result);
return result;
};
const loop = (data) =>
data.map((item) => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key
};
});
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={onChange}
/>
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(treeData)}
filterTreeNode={filterTreeNode}
/>
</div>
);
};
ReactDOM.render(<SearchTree />, document.getElementById("container"));
Couple weeks ago, i had to meet same like this client's enhancement. So how i did it was, we had Search over DirectoryTree and inside tree we rendered the nodes with a function
<DirectoryTree
onExpand={handleOperatorExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onSelect={handleOperatorSelect}
>
{renderTreeNodes(mtoCharts)}
</DirectoryTree>
In search onChange event we saved values to a state, like searchValue. Then searchValue was checked in renderTreeNodes function with regex:
let dynamicRegex = `.*(${searchValue.toUpperCase()}){1}.*`;
if
item.name.match(dynamicRegex)
then we display the node otherwise we dont. SIMPLE !
UPDATE : codes
<Search onChange={handleOperatorChange} />
const handleOperatorChange = e => {
const { value } = e.target;
let temp = value.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()"'+#<>?\\\[\]]/g, '');
setSearchValue(temp);
setAutoExpandParent(true);};
and finally renderTreeNodes :
const renderTreeNodes = data =>
data &&
data instanceof Array &&
data.map(item => {
const title = (
<Highlighter
highlightStyle={{ color: '#f50' }}
searchWords={[searchValue]}
autoEscape={true}
textToHighlight={item.name}
/>
);
if (searchValue) {
let dynamicRegex = `.*(${searchValue.toUpperCase()}){1}.*`;
if (!isEmpty(item.children)) {
// let doesChildrenHaveMatchingName = false;
// item.children.forEach(itemChild => {
// if (itemChild.name.match(dynamicRegex)) {
// doesChildrenHaveMatchingName = true;
// }
// });
// if (doesChildrenHaveMatchingName) {
return (
<TreeNode
className={!item.active ? 'dim-category' : ''}
key={item.code}
title={title}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
)}
>
{renderTreeNodes(item.children)}
</TreeNode>
);
// }
}
if (item.name.match(dynamicRegex) || item.path.match(dynamicRegex)) {
return (
<TreeNode
className={item.active ? 'false' : 'dim-category'}
key={item.code}
title={!item.leaf ? title : getMtoTitle(item, title)}
{...(item.leaf ? { leaf: item.leaf } : {})}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => {
return item.leaf ? (
<Icon type="money-collect" style={{ color: '#47bfa5' }} />
) : (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
);
}}
/>
);
}
} else {
if (!isEmpty(item.children)) {
return (
<TreeNode
className={!item.active ? 'dim-category' : ''}
key={item.code}
title={title}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
)}
>
{renderTreeNodes(item.children)}
</TreeNode>
);
}
return (
<TreeNode
className={item.active ? 'false' : 'dim-category'}
key={item.code}
title={!item.leaf ? title : getMtoTitle(item, title)}
{...(item.leaf ? { leaf: item.leaf } : {})}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => {
return item.leaf ? (
<Icon type="money-collect" style={{ color: '#47bfa5' }} />
) : (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
);
}}
/>
);
}
});
I have a tsx file which creates a table of comments. When the page is rendered, an array of information representing comments is gathered. A Set containing the indexes of the comments in the array, which is part of the state, determines whether a link under the comment reads 'show more' or 'show less'. Once that link is clicked, the index of the comment is added to the Set, and the state is updated with the addition of that index to the Set, if the Set contains the index of a comment when the state is updated, it should read 'show less'. The problem is when I click that link, it changes the link of each list element in the table, not just one.
import * as React from "react";
import { LocalizationInfo } from "emt-localization";
import { DateHandler } from "../handlers/date-handler";
import { UserIcon } from "./user-icon";
import { OnDownloadDocument } from "../models/generic-types";
import { getUserString } from "../models/user";
import { ClaimAction, ActionDetails, ReasonActionDetails, CommentDetails, DocumentDetails } from "../models/action";
const stateChangeActions = new Set<string>([
'Dispute',
'Rejection',
'SetUnderReview',
'Approval',
'Recall',
'Submission',
'RequestPartnerAction'
]);
const fiveMinutes = 5 * 60 * 1000;
const underscoreRegex = /_[^_]*_/g;
const actionTypeClassMap: {[actionType: string]: string} = {
'Submission': 'success',
'RequestPartnerAction': 'warn',
'Rejection': 'warn',
'Approval': 'success',
'Recall': 'warn',
'Dispute': 'warn',
'SetUnderReview': 'info',
'CustomerConsentDeclined': 'rejected'
};
const maxShortLength = 100;
interface ActionsProps {
readonly localization: LocalizationInfo;
readonly actions: ClaimAction[];
readonly onDownloadDocument: OnDownloadDocument;
readonly isInternalFacing: boolean;
readonly isHistoryPaneDisplay: boolean;
}
class ActionsState {
readonly expandedComments = new Set<number>();
}
export class Actions extends React.Component<ActionsProps, ActionsState> {
constructor(props: ActionsProps) {
super(props);
this.state = new ActionsState();
}
render():JSX.Element {
const loc = this.props.localization;
const isInternalFacing = this.props.isInternalFacing;
const toTime = (action:ClaimAction) => new Date(action.timeStamp).getTime();
const sortBy = (a:ClaimAction, b:ClaimAction) => toTime(b) - toTime(a);
const actions = this.props.actions.filter(action => {
if (isDocumentDetails(action.details)) {
if (action.actionType == 'DocumentSubmission' && action.details.documentType == 'Invoice') {
return false;
}
}
return true;
});
actions.sort(sortBy);
const grouped = groupActions(actions);
return (
<ul className={`claim-actions ${this.props.isHistoryPaneDisplay ? '' : 'user-comment-box'}`}>
{grouped.map((actions, index) => {
let actionClass = '';
actions.forEach(action => {
actionClass = actionClass || actionTypeClassMap[action.actionType];
});
const first = actions[0];
const icon = actionClass == 'success' ?
sequenceIcon('complete') :
actionClass == 'warn' ?
sequenceIcon('action-required') :
actionClass == 'rejected' ?
sequenceIcon('rejected') :
actionClass == 'info' ?
sequenceIcon('editing') :
<UserIcon
user={first.user}
isInternalFacing={isInternalFacing}
/>;
const elements = actions.map((action, actionIndex) => this.renderAction(action, actionIndex));
return (
<li className={actionClass} key={index}>
{icon}
<div className="win-color-fg-secondary">
<span className="claim-action-name win-color-fg-primary">
{ getUserString(first.user, isInternalFacing) }
</span>
<span className="text-caption">
{loc.piece("HistoryItemTitle", 0)}
{DateHandler.friendlyDate(first.timeStamp, true)}
</span>
</div>
{elements}
</li>
)
})}
</ul>
)
}
private renderAction(action:ClaimAction, actionIndex:number):JSX.Element|null {
const strings = this.props.localization.strings;
if (action.actionType == 'AddComments' || action.actionType == 'AddInternalComments') {
return this.renderComment((action.details as CommentDetails).comments, actionIndex);
}
const document = isDocumentDetails(action.details) ? action.details : null;
const documentLink = document ?
(key:number) =>
<a
key={key}
onClick={() => this.props.onDownloadDocument(document.documentId, document.name)}>
{document.name}
</a>
: null;
const locKey = `History_${action.actionType}`;
const localizedFlat = strings[locKey] || "";
const localized = replaceUnderscores(localizedFlat, documentLink);
const reason = (action.actionType === 'RequestPartnerAction' || action.actionType === 'Rejection') && action.details ? (action.details as ReasonActionDetails).reasonCode : '';
const reasonString = reason.charAt(0).toUpperCase() + reason.slice(1)
if (localized) {
return (
<div key={actionIndex}>
<div className="claim-action">
<span className="text-caption">{localized}</span>
</div>
<div className="claim-action">
{ reasonString && <span className="text-caption"><strong>{strings['ReasonLabel']}</strong>{` ${strings[reasonString]}`}</span> }
</div>
</div>
);
}
console.error(`Unknown action type ${action.actionType}`);
return null;
}
private renderComment(comment: string, actionIndex: number): JSX.Element {
const strings = this.props.localization.strings;
const canShorten = comment.length > maxShortLength;
const shouldShorten = canShorten && !this.state.expandedComments.has(actionIndex);
const shortened = shouldShorten ?
comment.substring(0, maxShortLength) + "\u2026" :
comment;
const paragraphs = shortened
.split('\n')
.map(s => s.trim())
.filter(s => s);
const elements = paragraphs.map((comment, i) =>
<div className="claim-comment" key={i}>
{comment}
</div>
);
const toggle = () => {
const next = new Set<number>(this.state.expandedComments);
if (next.has(actionIndex)) {
next.delete(actionIndex)
}
else {
next.add(actionIndex);
}
this.setState({ expandedComments: next });
}
const makeLink = (locKey:string) =>
<a
onClick={toggle}>{strings[locKey]}</a>;
const afterLink = canShorten ?
shouldShorten ?
makeLink('ShowMore') :
makeLink('ShowLess') :
null;
return (
<React.Fragment key={actionIndex}>
{elements}
{afterLink}
</React.Fragment>
);
}
}
// Function groups actions together under some conditions
function groupActions(actions:ClaimAction[]):ClaimAction[][] {
const grouped:ClaimAction[][] = [];
actions.forEach(action => {
if (grouped.length) {
const lastGroup = grouped[grouped.length - 1];
const timeDifference = new Date(lastGroup[0].timeStamp).getTime() - new Date(action.timeStamp).getTime();
if (stateChangeActions.has(lastGroup[0].actionType) && action.actionType == 'AddComments' && timeDifference < fiveMinutes) {
lastGroup.push(action);
return;
}
}
grouped.push([action]);
});
return grouped;
}
function isDocumentDetails(details:ActionDetails|null): details is DocumentDetails {
return !!details && (details.$concreteClass == 'InvoiceActionDetails' || details.$concreteClass == 'DocumentActionDetails');
}
function sequenceIcon(className: string):JSX.Element {
return (
<div className="sequence sequence-status claims-icon">
<div className={`step ${className}`} />
</div>
);
}
function replaceUnderscores(str: string, documentLink: ((k:number)=>JSX.Element)|null, startKey: number=0):JSX.Element[] {
if (!str) {
return [];
}
const match = underscoreRegex.exec(str);
if (!match) {
return replaceDocumentLink(str, documentLink, startKey);
}
const firstText = str.substring(0, match.index);
const middleText = match[0].substring(1, match[0].length - 1);
const lastText = str.substring(match.index + match[0].length);
const first = replaceUnderscores(firstText, documentLink, startKey);
const middle = [<strong className={'claims-emphasis'} key={startKey + first.length}>{middleText}</strong>];
const last = replaceUnderscores(lastText, documentLink, startKey + first.length + 1);
return first.concat(middle, last);
}
function replaceDocumentLink(str: string, documentLink: ((k:number)=>JSX.Element)|null, startKey: number=0):JSX.Element[] {
const replaceIndex = str.indexOf('{0}');
if (replaceIndex >= 0 && documentLink) {
return [
<React.Fragment key={startKey}>{str.substring(0, replaceIndex)}</React.Fragment>,
documentLink(startKey+1),
<React.Fragment key={startKey + 2}>{str.substring(replaceIndex+3)}</React.Fragment>
];
}
return [<React.Fragment key={startKey}>{str}</React.Fragment>];
}
I created a shopping list. Each item in the list has 2 options: "Remove" (will remove the specific item from the shopping list), and "Edit" (let the user changing the size or/and the quantity of the specific item). My problem is when I'm trying to edit the items by changing the size, quantity, and removing some of them, everything works but after several items that I'm changing/removing, the quantity and the sizing lists aren't updating in the right way. I have been trying so many different approaches but couldn't find out what is the root of the problem.
import React, { Component } from 'react';
import Header from './Components/header';
import Item from './Components/item';
import ItemProperties from './Components/itemProperties';
import { shirtsList } from './shirtsList';
import CustomerHelp from './Components/customerHelp';
import Checkout from './Components/checkout';
import EditPage from './Components/editPage';
class App extends Component {
constructor(props) {
super(props);
this.state = {
quantity: {},
totalItems: 0,
sizing: {},
editItem: [],
cart: shirtsList
}
}
// Item component
handleRemoveItem(shirt) {
const newCart = this.state.cart.filter((item) => {
return shirt.id !== item.id
})
this.setState({cart: newCart})
//--------------------------------------------------------//
const newQuantity = this.state.quantity.filter((item) => {
return shirt.id !== item.id
})
this.setState({quantity: newQuantity})
//--------------------------------------------------------//
const newSizing = this.state.sizing.filter((item) => {
return shirt.id !== item.id
})
this.setState({sizing: newSizing})
//--------------------------------------------------------//
let totall = 0;
for(let i = 0; i < newQuantity.length; i++){
totall = totall + newQuantity[i].amount;
}
this.setState({totalItems: totall})
}
openItemEvent(shirt) {
const itemToEdit = this.state.cart.filter((item) => {
return shirt.id === item.id
})
this.setState({editItem: itemToEdit})
document.querySelector('.mainEditPage').style.display = 'flex';
}
// Checkout component
subTotalPrice = () => {
let total = 0;
let quantity = this.state.quantity;
let cart = this.state.cart;
for(let i = 0; i < quantity.length; i++){
if(cart[i].price === cart[i].oldPrice && cart.length > 0){
total = total + quantity[i].amount * cart[i].price;
} else{
total = total + quantity[i].amount * cart[i].price;
}
}
return total.toFixed(2);
}
// EditPage component
changingSize = (event, shirt) =>{
let currentSize = event.target.value;
this.newSizes = this.state.sizing.map((i, num) => {
if(i.id === shirt.id){
return {size: currentSize,id: num+1}
}else {
return {size: i.size ,id: num+1}
}
})
}
changingQuantity = (event, shirt) =>{
let currentQuantity = event.target.value;
this.newQuantity = this.state.quantity.map((i, num) => {
if(i.id === shirt.id){
return {amount: Number(currentQuantity), id: num+1}
}else {
return {amount: Number(i.amount) ,id: num+1}
}
})
}
editButton = () =>{
let total = 0;
// Setting up "sizing" state after pressing the edit button
if(this.newSizes === undefined) {
this.setState({sizing: this.state.cart.map((shirt, i) => {
return {size: shirt.size,id: i+1}
})})
}else {
this.setState({sizing: this.newSizes})
}
// Setting up "quantity" and "totalItems" state after pressing the edit button
if(this.newQuantity === undefined) {
this.setState({quantity: this.state.cart.map((shirt, i) => {
return {amount: shirt.quantity,id: i+1}
})})
for(let i = 0; i < this.state.quantity.length; i++){
total = total + this.state.quantity[i].amount;
}
this.setState({totalItems: total})
}else {
this.setState({quantity: this.newQuantity})
for(let i = 0; i < this.newQuantity.length; i++){
total = total + this.newQuantity[i].amount;
}
this.setState({totalItems: total})
}
document.querySelector('.mainEditPage').style.display = 'none';
}
exitButton() {
document.querySelector('.mainEditPage').style.display = 'none';
}
// React lifeCycle methods
componentWillMount(){
this.getQuantity();
this.getSize();
}
componentDidMount(){
this.getTotalItems();
}
//--------------------------------------------------------//
getSize = () =>{
const array = shirtsList.map((shirt, i) => {
return {size: shirt.size,id: i+1}
})
this.setState({sizing: array})
}
getQuantity = () =>{
const array = shirtsList.map((shirt, i) => {
return {amount: shirt.quantity,id: i+1}
})
this.setState({quantity: array})
}
getTotalItems = () =>{
let total = 0;
for(let i = 0; i < this.state.quantity.length; i++){
total = total + this.state.quantity[i].amount;
}
this.setState({totalItems: total})
}
render() {
return (
<div>
<Header />
<ItemProperties amount={this.state.totalItems}/>
<div className="itemInTheCart">
{
this.state.cart.map((shirt, i) => {
return (
<Item
name={shirt.name}
price={shirt.price}
oldPrice={shirt.oldPrice}
color={shirt.color}
style1={shirt.style}
image={shirt.image}
quantity={this.state.quantity[i].amount}
size={this.state.sizing[i].size}
key={shirt.id}
handleRemoveItem={() => {this.handleRemoveItem(shirt)}}
openItemEvent={() => {this.openItemEvent(shirt)}}
/>
)
})
}
</div>
<div className="checkout__section">
<CustomerHelp />
<Checkout
change={this.subTotalPrice()}
/>
</div>
<div className="mainEditPage">
{
this.state.editItem.map((shirt) => {
return (
<EditPage
image={shirt.image}
name={shirt.name}
price={shirt.price}
style={shirt.style}
color={shirt.color}
key={shirt.id}
size={shirt.size}
exitButton={() => {this.exitButton()}}
changingSize={(event) =>{this.changingSize(event, shirt)}}
changingQuantity={(event) =>{this.changingQuantity(event, shirt)}}
editButton={() => {this.editButton()}}
/>
)
})
}
</div>
</div>
);
}
}
export default App;
I have a very basic usage of the React Virtualized MultiGrid component where I simply render a table of numbers going from 1 to 100.
For some reason tho, the first row will not get rendered. In other words, the table always starts at the number 2.
Here is my code.
const Container = {
width: "90%",
height: "100vh",
margin: "auto",
};
class App extends Component {
state = {
data: [],
sort: {
column: "",
descending: false,
},
};
componentDidMount() {
const numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i + 1);
}
const final = numbers.map(n => {
return {
single: n,
double: n * 2,
triple: n * 3
};
});
this.setState({ data: final });
}
cellRenderer = (data, columns) => {
if (data.rowIndex === 0) {
return (
<span
style={data.style}
key={data.key}
>
{columns[data.columnIndex]}
</span>
);
}
const column = columns[data.columnIndex];
return (
<span
style={data.style}
key={data.key}
>
{this.state.data[data.rowIndex][column]}
</span>
);
}
render() {
const columns = ["single", "double", "triple"];
return (
<div style={Container}>
<AutoSizer>
{({ width, height }) => (
<MultiGrid
columnWidth={70}
width={width}
height={height}
cellRenderer={(data) => this.cellRenderer(data, columns)}
fixedRowCount={1}
rowHeight={70}
columnCount={3}
rowCount={this.state.data.length}
/>
)}
</AutoSizer>
</div>
);
}
}
And here is a screenshot of my output.
Thanks to Doron's comment, I got it working.
Here is the code with the relevant changes.
const Container = {
width: "90%",
height: "100vh",
margin: "auto",
};
class App extends Component {
state = {
data: [],
sort: {
column: "",
descending: false,
},
};
componentDidMount() {
const numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i + 1);
}
const final = numbers.map(n => {
return {
single: n,
double: n * 2,
triple: n * 3
};
});
this.setState({ data: final });
}
cellRenderer = (data, columns) => {
if (data.rowIndex === 0) {
return (
<span
style={data.style}
key={data.key}
>
{columns[data.columnIndex]}
</span>
);
}
const column = columns[data.columnIndex];
return (
<span
style={data.style}
key={data.key}
>
{this.state.data[data.rowIndex - 1][column]}
</span>
);
}
render() {
const columns = ["single", "double", "triple"];
return (
<div style={Container}>
<AutoSizer>
{({ width, height }) => (
<MultiGrid
columnWidth={70}
width={width}
height={height}
cellRenderer={(data) => this.cellRenderer(data, columns)}
fixedRowCount={1}
rowHeight={70}
columnCount={3}
rowCount={this.state.data.length + 1}
/>
)}
</AutoSizer>
</div>
);
}
}
Notice that now the total row count is actually 1 more than the length of the data array, and then when indexing into the array in my cellRenderer, I index by 1 less than the current index.