i have crated a react table , to display my product variation showing its id , variation sunch weight,width,height and so on, also its price and add to cart button in the table, I wanted to loop the header so that , the each variation name is a header in the table. i have save the data in state . below is my code of table function which I created
<table {...getTableProps()} className="w-100">
<thead>
{headerGroups.map((headerGroup) => (
<tr key={headerGroup.id} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th
className={classnames(
" text-left pl-0 rt-th rt-resizable-header",
{
"-cursor-pointer": column.sortable,
"-sort-asc": column.isSorted && !column.isSortedDesc,
"-sort-desc": column.isSorted && column.isSortedDesc,
}
)}
key={column.id}
{...column.getHeaderProps()}
>
<div
className="rt-resizable-header-content"
{...(column.sortable && {
...column.getHeaderProps(column.getSortByToggleProps()),
})}
>
{column.render("Header")}
</div>
{/* Render the columns filter UI */}
<div>
{column.canFilter ? column.render("FilterColumn") : null}
</div>
</th>
))}
</tr>
))}
</thead>
<tbody className="text-left" {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr
key={row.id}
{...row.getRowProps()}
className={classnames(
"rt-tr text-break",
{ " -odd": i % 2 === 0 },
{ " -even": i % 2 === 1 }
)}
>
{row.cells.map((cell) => (
<td key={row.id} {...cell.getCellProps()}>
{cell.render("Cell")}
</td>
))}
</tr>
);
})}
</tbody>
</table>
below is how I save my data in the data state using useEFFECT inreact
React.useEffect(() => {
setDataState(
variation.map((variations) => ({
sn: variations.VariationSKU,
snId: <p className="megan-text">{variations.VariationSKU}</p>,
attributes: variations.Attributes.map((attribute) => (
<td key={attribute.ID} className="text-dark text-center">
<p className="mr-5">{attribute.Value}</p>
</td>
)),
price: <CurrencyFormat price={variations.Price} currency="MYR" />,
action: (
<Button
id="Sproduct-btn"
className={`btn add-to-cart w-100 ${submitResponse.class} `}
data-toggle="tooltip"
type="button"
tag={!user ? Link : "a"}
to={!user ? "/login" : ""}
disabled={
submitResponse.openState ||
status === "Inactive" ||
variationstatus === "Inactive" ||
stock === 0 ||
variationstock === 0
}
style={{ fontSize: "15px", width: "30%" }}
{...(user && {
onClick: () => {
onSubmit(productid, variations.ID);
// setResponse(addToCart(productid, variation.ID));
},
})}
>
<div className="cart">
<div>
<div />
<div />
</div>
</div>
<div className="dots" />
{/* {variationStock === 0 || data.data.Stock === 0 ? (
<div className="pl-2 default">SOLD OUT</div>
) : (
<span className="pl-2 default">ADD TO BAG</span>
)} */}
<span className="pl-2 default">{result}</span>
<div className="success">ADDED</div>
<div className="failed">FAILED TO ADDED, PLEASE TRY AGAIN</div>
</Button>
),
}))
);
}, [submitResponse]);
const getTableHeaders = () =>
variation.map((varian) =>
varian.Attributes.map((attribute) => ({
id: attribute.ID,
Header: attribute.Name,
accessor: String(attribute.Value),
sortable: true,
}))
);
these are how I call the data in where I wanted the attribute name, and value in their own column in the table. Currently, my variation are in 1 column but I wanted each attributes have its own column which I don't know how to do, can anyone pls help, thank you
{dataState !== [] && (
<Table
responsive
columns={[
{
Header: "SN",
accessor: "snId",
sortable: true,
},
{
...getTableHeaders(),
},
{
Header: "Price",
accessor: "price",
sortable: true,
},
{
Header: "",
accessor: "action",
},
]}
data={dataState}
className="-striped -highlight primary-pagination font-weight-bold"
/>
)}
this how my table looks like
these the response of variation which I used in getTableHeader
"VariationList": [
{
"ID": "63eaf4417f339ebdb324fe33",
"VariationSKU": "102100489N",
"Price": 2250,
"Attributes": [
{
"ID": "63eaf3167f339ebdb324efa9",
"Name": "Hook",
"Value": "CAKUK S"
},
{
"ID": "63eaf3167f339ebdb324efa7",
"Name": "Weight",
"Value": 3.8
},
{
"ID": "63eaf3167f339ebdb324efaa",
"Name": "Length",
"Value": 28
},
{
"ID": "63eaf3167f339ebdb324efa4",
"Name": "Width",
"Value": 1.3
}
],
},{},{},{},{}
[![enter image description here][2]][2]
Looks like you're only returning one column object for all three attributes. Rather than doing a map inside 1 column object, you could do a map outside. You should also have an id field in all columns with accessors - I would suggest a function like:
const getTableHeaders = (attributelist) =>
attributelist.map((attribute) => ({
id: attribute.ID,
Header: attribute.Name,
accessor: String(attibute.Value),
sortable: true,
}));
And then, you could use it like this:
columns = {[
{
Header: "SN",
accessor: "snId",
sortable: true,
},
...getTableHeaders(attributelist),
{
Header: "Price",
accessor: "price",
sortable: true,
},
{
Header: "",
accessor: "action",
},
]};
The key thing you're missing is that you should have 1 object per column, whereas you currently have 1 object for all 3 desired columns.
Related
I am fetching data from a backend endpoint and I want to display this data by using a component I found that takes as props the columns and the rows. I went ahead and created the columns(header) but I don't know how to handle the json response as it's array of objects.
Before I used a for the columns and with the map function to sort the data to rows. The objects have more than just 6 columns but I only wanted to display the important ones.
Take a look in my code to see the previous method I used and please provide any help how I can do it with the new way.
const Trademarks = () => {
const navigate = useNavigate();
const user = useUser();
const [trademarks, setTrademarks] = useState(null);
useEffect(() => {
obtainFromServer("api/trademarks", "GET", user.jwt).then(trademarksData => {
setTrademarks(trademarksData);
});
if(!user.jwt){
navigate("/login");
}
},[user.jwt]);
const trademarksColumns = {
columns: [
{ Header: "action", accessor: "id", width: "7%" },
{ Header: "cy number", accessor: "cynumber", width: "13%" },
{ Header: "ir number", accessor: "irnumber", width: "16%" },
{ Header: "Date Publish", accessor: "dtpublish", width: "15%" },
{ Header: "Status", accessor: "status", width: "15%" },
{ Header: "Notes", accessor: "notes" },
],
}
const trademarksRows = {
rows: [
{
id: "i",
cynumber: "i",
irnumber: "i",
dtpublish: "i",
status: "i",
notes: "i",
},
{
id: "ai",
cynumber: "ai",
irnumber: "ai",
dtpublish: "ai",
status: "ai",
notes: "ai",
}
],
}
console.log(trademarks);
function getRolesFromJWT(){
if(user.jwt){
const decodedJwt = jwt_decode(user.jwt);
return decodedJwt.authorities;
}
return [];
}
function LogOut (){
fetch("/api/auth/logout").then((response) => {
if (response.status === 200) {
user.setJwt(null);
// navigate(0);
}
});
}
return (
<>
<Box my={3}>
<Table striped bordered hover variant="dark">
<thead>
<tr style={{ textAlign: 'center' }}>
<th>Action</th>
<th>CY Number</th>
<th>IR Number</th>
<th>Date Publish</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{trademarks ? trademarks.map(trademark =>
<tr key={trademark.id} style={{ textAlign: 'center' }}>
<td><Link to={`/trademarks/${trademark.id}`}>View/Edit</Link></td>
<td>{trademark.cynumber}</td>
<td>{trademark.irnumber}</td>
<td>{trademark.dtpublish}</td>
<td>{trademark.status}></td>
<td>{trademark.notes}</td>
</tr>) : <></>}
</tbody>
</Table>
</Box>
<Box pt={6} pb={3}>
<Card>
<Box p={3} lineHeight={1}>
<Typography variant="h5" fontWeight="medium">
Datatable Search
</Typography>
</Box>
<DataTable columnsTitle={trademarksColumns} rowsData={trademarksRows} canSearch />
</Card>
</Box>
</>
);
};
export default Trademarks;
Basically, I need to put the JSON response but only the needed columns inside the constant trademarksRows inside the rows array. Lastly for each row previously I included a button so the user can navigate to the trademark by the id.
What's the best way to solve my problem and how can I do that?
Finally, figure it out. This is how I did it.
const [trademarksRows, setTrademarksRows] = useState({
rows: [],
})
useEffect(() => {
obtainFromServer("api/trademarks", "GET", user.jwt).then(trademarksData => {
setTrademarks(trademarksData);
let rowsTr = trademarksData.map(trademarkData => ({
id: <MDButton color="secondary" onClick={() => (navigate(`/trademarks/${trademarkData.id}`))}>View/Edit</MDButton>,
cynumber: trademarkData.cynumber,
irnumber: trademarkData.irnumber,
dtpublish: trademarkData.dtpublish,
status: <StatusBadge text={trademarkData.status}/>,
notes: trademarkData.notes,
}));
setTrademarksRows({rows:rowsTr});
});
},[]);
what I try to do is to have the same display as this picture :
So in my menu the plant type (Type of plant1) is displayed above a gray bar and when you click on the down chevron then you can see all the plants name, related to this type, with checkboxes on left, by default there will be all checked. And the blue rectangle indicates the number of plants that have been selected.
How can I do that, which package can help me in REACT?
Here my plants.json :
{
"plants_type": [
{
"_id_type": "1",
"name_type": "Type of plant1",
"plants": [
{
"name": "Plant1.1",
"_id": "2"
},
{
"name": "Plant1.2",
"_id": "3"
}
]
},
{
"_id_type": "4",
"name_type": "Type of plant2",
"plants": [
{
"name": "Plant2.1",
"_id": "5"
},
{
"name": "Plant2.2",
"_id": "6"
}
]
}
]
}
You can create a dropdown list on your own like below. I have added the logic of selecting items to the data itself.
You can keep a component called Category to keep a single state of the parent menu item. Whether it's open or not. Then iterate over the plants as checkbox inputs to make them selectable.
I have used a simple initialize function to make all the items selected initially. This should work as you expect. Add a console log of selectionMenu to see how selected property changes while toggling items.
Move the inline styles to CSS classes to make the code more clear.
const data = { plants_type: [ { _id_type: "1", name_type: "Type of plant1", plants: [ { name: "Plant1.1", _id: "2" }, { name: "Plant1.2", _id: "3" } ] }, { _id_type: "4", name_type: "Type of plant2", plants: [ { name: "Plant2.1", _id: "5" }, { name: "Plant2.2", _id: "6" } ] } ] };
const Category = ({ _id_type, name_type, plants, changeSelection }) => {
const [toggleState, setToggleState] = React.useState(false);
return (
<div key={_id_type}>
<div
style={{
cursor: "pointer",
userSelect: "none",
display: "flex",
margin: "2px",
backgroundColor: "lightgray"
}}
onClick={() => setToggleState((prev) => !prev)}
>
<div>{name_type}</div>
<div
style={{
backgroundColor: "blue",
color: "white",
padding: "0px 10px",
marginLeft: "auto"
}}
>
{plants.filter(({ selected }) => selected).length}
</div>
</div>
<div style={{ marginLeft: "10px" }}>
{toggleState &&
plants.map(({ name, _id, selected }) => (
<div key={_id}>
<input
key={_id}
type="checkbox"
value={name}
checked={selected}
onChange={(e) => changeSelection(_id_type, _id, e.target.value)}
/>
{name}
</div>
))}
</div>
</div>
);
};
const App = () => {
const initializeSelectionMenu = (data) => {
return data.map((item) => {
return {
...item,
plants: item.plants.map((plant) => ({ ...plant, selected: true }))
};
});
};
const [selectionMenu, setSelectionMenu] = React.useState(
initializeSelectionMenu(data.plants_type)
);
console.log(selectionMenu);
const changeSelection = (catId, itemId, value) => {
setSelectionMenu((prevSelectionMenu) =>
prevSelectionMenu.map((item) => {
if (item._id_type === catId) {
return {
...item,
plants: item.plants.map((plant) => {
if (plant._id === itemId) {
return { ...plant, selected: !plant.selected };
}
return plant;
})
};
}
return item;
})
);
};
return (
<div>
{selectionMenu.map((item) => (
<Category
{...item}
changeSelection={changeSelection}
key={item._id_type}
/>
))}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
I'm having issues with deleting elements in react-flow via a button.
I can delete elements fine using Backspace but the button only works for the first delete and after that it brings back the deleted node.
New to using react-flow and can't put my finger on the problem here. Is the state not getting changed some how?
Below is the code I use for react flow
CodeSandbox here.
import React, { useState, useCallback, useEffect } from "react";
import ReactFlow, {
removeElements,
addEdge,
Background,
} from "react-flow-renderer";
const onLoad = (reactFlowInstance) => {
reactFlowInstance.fitView();
console.log(reactFlowInstance.getElements());
};
const StackFlow = () => {
const initialElements = [
{
id: "0",
position: { x: 0, y: -100 },
sourcePosition: "bottom",
style: {
width: 100,
fontSize: 11,
color: "white",
background: "#6ec9c0",
},
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("0")
}
>
Del
</button> <br /> <br />
<strong>Models</strong>
</>
),
},
},
{
id: "1",
position: { x: 100, y: 50 },
sourcePosition: "bottom",
targetPosition: "top",
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("1")
}
>
Del
</button> <br /> <br />
Model: <strong>1</strong> <br />
ID: 1
</>
),
},
},
{
id: "2",
position: { x: 150, y: 250 },
sourcePosition: "bottom",
targetPosition: "top",
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("2")
}
>
Del
</button> <br /> <br />
Model 1: <strong>subModel1</strong> <br />
ID: 2
</>
),
},
},
{
id: "3",
position: { x: 250, y: 250 },
sourcePosition: "bottom",
targetPosition: "top",
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("3")
}
>
Del
</button> <br /> <br />
Model 1: <strong>subModel2</strong> <br />
ID: 3
</>
),
},
},
{
id: "0-1",
type: "step",
source: "0",
target: "1"
},
{
id: "1-2",
type: "step",
source: "1",
target: "2"
},
{
id: "1-3",
type: "step",
source: "1",
target: "3"
},
];
const [elements, setElements] = useState(initialElements);
const onElementsRemove = useCallback(
(elementsToRemove) =>
setElements((els) => removeElements(elementsToRemove, els)),
[]
);
const onConnect = (params) => setElements((els) => addEdge(params, els));
const [reactflowInstance, setReactflowInstance] = useState(null);
useEffect(() => {
if (reactflowInstance && elements.length > 0) {
reactflowInstance.fitView();
}
}, [reactflowInstance, elements.length]);
const remModelData = useCallback((id) => {
let arr = elements
var index = arr.indexOf(id);
if (index > -1) {
arr.splice(index, 1);
}
arr = arr.filter(function (obj) {
return obj.id !== id;
});
console.log(arr);
setElements(arr);
}, []);
return (
<ReactFlow
elements={elements}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onLoad={onLoad}
snapToGrid={true}
snapGrid={[15, 15]}
>
<Background color="#aaa" gap={16} />
</ReactFlow>
);
};
function Home() {
return (
<>
<div className="w-full mx-auto justify-center items-center flex">
<div
className="flex mt-10 flex-row items-center content-center justify-center max-h-5xl max-w-5xl py-2"
style={{ flex: 1, width: 1000, height: 800, borderWidth: 2 }}
>
<StackFlow />
</div>
</div>
</>
);
}
export default Home;
I was able to accomplish the end result I wanted by creating this function:
const deleteNode = (id) => {
setElements((els) => removeElements([elements[id]], els));
};
And passing this into the OnClick:
onClick={() => deleteNode(0)}
Changing 0 for what ever index the element you wish to remove from the array is
As from version 10 Elements has been replaced with Nodes and Edges.
I could achieve the following using the function below.
const deleteNodeById = (id) => {
flowInstance.setNodes((nds) => nds.filter((node) => node.id !== id))
}
And passing this function to my delete button (In my case mui delete icon)
<DeleteOutlined
onClick={() => deleteNodeById(data.id)}
style={{ color: '#FF0000' }}
/>
As of version 11.2 onwards, you can use
reactFlowInstance.deleteElements({ nodes: nodesToDelete, edges: edgesToDelete });
The advantage is that method will fire "onNodesDelete" & "onEdgesDelete" events so that you can reuse your handlers.
Recall that these events are also fired if you press the "backspace" button.
I have a list of items that needs to be recursively rendered. I am able to render it upto first level. Can someone help me with the nested recursion. I have posted the relevant functions in a function component. Note that details has been stored in a state variable details
{
"details": [{
"title": "Part-1",
"content": [{
"type": "text",
"content": "TextABC"
}]
}, {
"title": "Part-2",
"content": [{
"type": "text",
"content": "TextXYZ"
}, {
"type": "list",
"content": [{
"text": "TextLMN",
"type": "text"
}, {
"type": "list",
"content": [{
"text": "TextPQR",
"type": "text"
}, {
"text": "TextDEF",
"type": "text"
}]
}]
}]
}, {
"title": "Part-3",
"content": [{
"type": "list",
"content": ["<a target='_blank' href='https://www.example.com'>ABC<\/a>", "<a target='_blank' href='https://www.example1.com'>XYZ<\/a>"]
}]
}]
}
Each Item is referenced either with a type that can be text or a list. I have tried the following, but the items with nested list is not working
const isAnchor = str => {
return /^\<a.*\>.*\<\/a\>/i.test(str);
};
const getContentJsx = (value) => {
return isAnchor(value) ? (
<li dangerouslySetInnerHTML={{ __html: sanitize(value) }} />
) : (
<li>{value}</li>
);
};
const getDetailJsx = () => {
return details.map(({ title, content }, index) => {
return (
<div key={`${title}${index}`}>
<h6>
<span>{title}</span>
</h6>
{content?.map(({ type: mainType, content: data }) => (
<div>
{mainType === "text" && <p>{data}</p>}
{mainType === "list" && <ul>{data?.map((contentValue) => getContentJsx(contentValue))}</ul>}
</div>
))}
</div>
);
});
};
return (
<div>
<>
{getDetailJsx()}
</>
)}
</div>
);
As suggested, you can create a recursive component that wraps the logic and makes it possible to call recursively. First, I had to correct your detail object. It has inconsistency within its attributes. Note, for example, text -> content:
"type": "text",
"text": "TextPQR"
to
"type": "text",
"content": "TextPQR"
Moreover, observe that it was separated the part that involves the title from the recursion to facilitate its comprehension and make code cleaner.
const getContentJsx = (value) => {
return isAnchor(value) ? (
<li dangerouslySetInnerHTML={{ __html: sanitize(value) }} />
) : (
<li>{recursive(value)}</li>
);
};
const getDetailJsx = () => {
return details.map(({ title, content }, index) => {
return (
<div key={`${title}${index}`}>
<h3>
<span>{title}</span>
</h3>
{recursive(content)}
</div>
)
})
}
const recursive = (content) => {
return (
<div>
{
content.map(({type: mainType, content: data}, index) => (
<div key={index}>
{mainType === "text" && <p>{data}</p>}
{mainType === "list" && <ul>{getContentJsx(data)}</ul>
}
</div>
))
}
</div>
)
Also as shown here
I'm trying to do a simple filter based on input text, but it has a bug, when i press 'backspace', it should refilter based on the original arrays, not the previous filtered array. I know what the problem is: I changed the original tasks every time when it filters. And i know i should do something like making a copy of the original arrays and filter based on the copy. But i just don't know how to achieve that in react.
Below is my code:
export class Main extends Component {
constructor(pros) {
super(pros)
this.state = {
tasks: [
{
id: 1,
content: "Sara's doctor and vaccine",
due: '2020-08-29',
completed: false
},
{
id: 2,
content: "Trash bags / facial masks / child allowance",
due: '2020-08-28',
completed: false
},
{
id: 3,
content: "Apply for Portugal nationality",
due: '2020-09-31',
completed: false
},
{
id: 4,
content: "My registration card",
due: '2020-09-28',
completed: false
}
]
}
handleSearch = (e) => {
let searchValue = e.target.value
console.log(searchValue)
let filteredTasks = this.state.tasks.filter(task => {
return task.content.toLowerCase().includes(searchValue.toLowerCase())
})
this.setState(state => ({
tasks: filteredTasks
}))
}
render() {
return (
<div>
<div style={{ textAlign: 'right' }}><input type='search' onKeyUp={this.handleSearch} id='search' name='search' placeholder='Search Tasks' autoComplete='on' style={{ width: '60%', margin: '15px 15px 45px auto' }} /></div>
<table>
<caption>Good {this.state.dayPeriod}! ♥ {this.state.userName}</caption>
<thead>
<tr>
<th>
<button type='button' onClick={this.handleSelect}>Select All</button>
</th>
<th>ID</th>
<th>Content</th>
{/* <th>Created On</th> */}
<th>Due</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{this.state.tasks.reverse().map((el, i) => (
<tr key={i} className='row' style={{ textDecoration: el.completed ? this.state.textDecoration : 'none', color: el.completed ? this.state.color : '#000000' }}>
<td>
<input type='checkbox' checked={el.completed} onChange={() => { el.completed = !el.completed }}></input>
</td>
<td className='taskID' style={{ verticalAlign: 'text-top' }}>{el.id}</td>
<td className='taskContent'>{el.content}</td>
{/* <td style={{whiteSpace: 'nowrap'}}>{new Date().getFullYear()}-{new Date().getMonth().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping:false})}-{new Date().getDate().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping:false})}</td> */}
<td style={{ whiteSpace: 'nowrap' }}>{el.due}</td>
<td>{el.completed === false ? 'N' : 'Y'}</td>
</tr>
))}
{/* {this.listTasks()} */}
</tbody>
</table>
</div>
)
}
}
There are many ways to achieve what you are asking, but simplest way is to not change the state, only change what should be rendered.
On top of your code I've added the filter, so the state remains the same but only the filter applies on the results:
export class Main extends Component {
constructor(pros) {
super(pros)
this.state = {
tasks: [
{
id: 1,
content: "Sara's doctor and vaccine",
due: '2020-08-29',
completed: false
},
{
id: 2,
content: "Trash bags / facial masks / child allowance",
due: '2020-08-28',
completed: false
},
{
id: 3,
content: "Apply for Portugal nationality",
due: '2020-09-31',
completed: false
},
{
id: 4,
content: "My registration card",
due: '2020-09-28',
completed: false
}
],
searchValue: ""
}
handleSearch = (e) => {
this.setState({ searchValue: e.target.value })
}
filterResults = () => {
if(!this.state.searchValue) return this.state.tasks
return this.state.tasks.filter(task => {
return task.content.toLowerCase().includes(this.state.searchValue.toLowerCase())
})
}
render() {
return (
<div>
<div style={{ textAlign: 'right' }}><input type='search' onKeyUp={this.handleSearch} id='search' name='search' placeholder='Search Tasks' autoComplete='on' style={{ width: '60%', margin: '15px 15px 45px auto' }} /></div>
<table>
<caption>Good {this.state.dayPeriod}! ♥ {this.state.userName}</caption>
<thead>
<tr>
<th>
<button type='button' onClick={this.handleSelect}>Select All</button>
</th>
<th>ID</th>
<th>Content</th>
{/* <th>Created On</th> */}
<th>Due</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{filterResults().reverse().map((el, i) => (
<tr key={i} className='row' style={{ textDecoration: el.completed ? this.state.textDecoration : 'none', color: el.completed ? this.state.color : '#000000' }}>
<td>
<input type='checkbox' checked={el.completed} onChange={() => { el.completed = !el.completed }}></input>
</td>
<td className='taskID' style={{ verticalAlign: 'text-top' }}>{el.id}</td>
<td className='taskContent'>{el.content}</td>
{/* <td style={{whiteSpace: 'nowrap'}}>{new Date().getFullYear()}-{new Date().getMonth().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping:false})}-{new Date().getDate().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping:false})}</td> */}
<td style={{ whiteSpace: 'nowrap' }}>{el.due}</td>
<td>{el.completed === false ? 'N' : 'Y'}</td>
</tr>
))}
{/* {this.listTasks()} */}
</tbody>
</table>
</div>
)
}
}
You should extract original Task List out of state and always filter on that. That way you will have reference of all Tasks every time you filter. Something like this.
const allTasks = [
{
id: 1,
content: "Sara's doctor and vaccine",
due: '2020-08-29',
completed: false
},
{
id: 2,
content: "Trash bags / facial masks / child allowance",
due: '2020-08-28',
completed: false
},
{
id: 3,
content: "Apply for Portugal nationality",
due: '2020-09-31',
completed: false
},
{
id: 4,
content: "My registration card",
due: '2020-09-28',
completed: false
}
];
export class Main extends Component {
constructor(pros) {
super(pros)
this.state = {
tasks: allTasks
}
}
handleSearch = (e) => {
let searchValue = e.target.value
console.log(searchValue)
let filteredTasks = allTasks.filter(task => {
return task.content.toLowerCase().includes(searchValue.toLowerCase())
})
this.setState(state => ({
tasks: filteredTasks
}))
}
render() {
return (
<div>
<div style={{ textAlign: 'right' }}><input type='search' onKeyUp={this.handleSearch} id='search' name='search' placeholder='Search Tasks' autoComplete='on' style={{ width: '60%', margin: '15px 15px 45px auto' }} /></div>
<table>
<caption>Good {this.state.dayPeriod}! ♥ {this.state.userName}</caption>
<thead>
<tr>
<th>
<button type='button' onClick={this.handleSelect}>Select All</button>
</th>
<th>ID</th>
<th>Content</th>
{/* <th>Created On</th> */}
<th>Due</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{this.state.tasks.reverse().map((el, i) => (
<tr key={i} className='row' style={{ textDecoration: el.completed ? this.state.textDecoration : 'none', color: el.completed ? this.state.color : '#000000' }}>
<td>
<input type='checkbox' checked={el.completed} onChange={() => { el.completed = !el.completed }}></input>
</td>
<td className='taskID' style={{ verticalAlign: 'text-top' }}>{el.id}</td>
<td className='taskContent'>{el.content}</td>
{/* <td style={{whiteSpace: 'nowrap'}}>{new Date().getFullYear()}-{new Date().getMonth().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping:false})}-{new Date().getDate().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping:false})}</td> */}
<td style={{ whiteSpace: 'nowrap' }}>{el.due}</td>
<td>{el.completed === false ? 'N' : 'Y'}</td>
</tr>
))}
{/* {this.listTasks()} */}
</tbody>
</table>
</div>
)
}
}
It's better to change the handleSearch function to:
handleSearch = (e) => {
setState({searchValue: e.target.value})
}
and handle the filter in your map:
this.state.tasks.reverse()
.filter(task => task.content.toLowerCase().includes(searchValue.toLowerCase()))
.map(...
You are overriding the source data. You should keep an variable that store the source data
const TASK = [
{
id: 1,
content: "Sara's doctor and vaccine",
due: "2020-08-29",
completed: false
},
{
id: 2,
content: "Trash bags / facial masks / child allowance",
due: "2020-08-28",
completed: false
},
{
id: 3,
content: "Apply for Portugal nationality",
due: "2020-09-31",
completed: false
},
{
id: 4,
content: "My registration card",
due: "2020-09-28",
completed: false
}
];
// ...
constructor(pros) {
super(pros);
this.state = {
tasks: [...TASK]
};
}
handleSearch = (e) => {
let searchValue = e.target.value;
console.log(searchValue);
let filteredTasks = TASK.filter((task) => {
return task.content.toLowerCase().includes(searchValue.toLowerCase());
});
this.setState((state) => ({
tasks: filteredTasks
}));
};
// ...
Codesandbox demo