I am using react-bootstrap-table2 to make HTML tables, I am using a checkbox inside my table to delete the items.
SO as per This link, it is mentioned how to get the selected row and then do the next part, here what I am doing is when I click on any checkbox row gets selected, and if I again select any row that I am adding to the array into my state like below
onSelect: (row, isSelect, rowIndex, e) => {
if (isSelect === true) {
setrowData((rowData) => [...rowData, row]);
} else {
// here i need to do something
}
},
My issue is when I unselect the row that value is not getting deleted from my array, and at the time of delete, I have all the data which I have selected once.
One more thing which is related to this is when I check any row I want to show delete button and when none of the rows is checked I want to hide the delete button, here select is giving me the boolean value for that, but it is working for each select once I am selecting multiple rows it shows up, but when I unselect any one of them the delete button hides
What I have done for that is something like below
setrowSelect((rowSelect) => [...rowSelect, isSelect]); // this one is inside onSelect callback given by the react-bootstrap-table2 library
{rowSelect && (
<button className="btn Secondary_Button_with_Icon">
<i className="ri-upload-cloud-line ri-lg"></i>Register
</button>
)}
My full working code
Use a filter method inside your else block to remove that unselected element from the array
onSelect: (row, isSelect, rowIndex, e) => {
if (isSelect === true) {
setrowData((rowData) => [...rowData, row]);
} else {
setrowData((rowData) => rowData.filter((x,i)=>i!==rowIndex))
}
setrowSelect((rowSelect) => [...rowSelect, isSelect]);
},
Note that, you don't need to maintain another state for controlling visibility of Delete button.
You can perfectly hide/show Delete based on rowData state.
Also the code you wrote for handling selected rows works perfectly well. Just get rid of rowSelect state and its handlers.
And update the rendering of your Delete button based on contents of your rowData as:
{
rowData.length ? (
<button className="btn btn-primary" onClick={Delete_device}>
<i className="ri-upload-cloud-line ri-lg"></i>Delete
</button>
)
: null
}
This is the forked sandbox from yours.
try to change onSelect function like this
onSelect: (row, isSelect, rowIndex, e) => {
if (isSelect === true) {
setrowData((rowData) => [...rowData, row]);
rowSelect.push(true);
setrowSelect(rowSelect);
} else {
setrowData((rowData) => rowData.filter((x, i) => i !== rowIndex));
rowSelect.pop();
setrowSelect(rowSelect);
}
}
Here is one way to implement what you want :
1.Keep your data in one object, and add an id and isSelect fields
const data = [
{
id: "id-1",
fname: "john",
lname: "smith",
isSelect: false
},
{
id: "id-2",
fname: "steve",
lname: "warn",
isSelect: false
},
{
id: "id-3",
fname: "michel",
lname: "clark",
isSelect: false
}
];
2.pass this data to useState :
const [rowData, setrowData] = useState(data);
3.onSelect : just find the row by id and set isSelect field
onSelect: (row, isSelect, rowIndex, e) => {
setrowData((rows) => {
return rows.map((r) => {
if (r.id !== row.id) {
return r;
}
return { ...r, isSelect };
});
});
},
4.onSelectAll set isSelect on all rows
onSelectAll: (isSelect, rows, e) => {
setrowData((rows) => {
return rows.map((row) => {
return { ...row, isSelect };
});
});
}
5.for Delete_device just filter the data that is not selected :
const Delete_device = () => {
setrowData((rows) => {
return rows.filter((row) => !row.isSelect);
});
};
6.for the delete button, get the selected rows and count them, if the count is > 0 then show the button :
const selectedRows = rowData.filter((row) => row.isSelect);
return (
<div className="App">
{selectedRows.length > 0 && (
<button className="btn btn-primary" onClick={Delete_device}>
<i className="ri-upload-cloud-line ri-lg"></i>Delete
</button>
)}
7.Pass the state data to BootstrapTable
<BootstrapTable
bootstrap4
keyField="fname"
data={rowData}
columns={tableData[0].columnsData}
selectRow={selectRow}
/>
Complete example
I updated your state to use your data and removed the select array from your select logic. I also optimized it a bit. Its minor change from your codesandbox sample. Also, I recommend you use ids.
import React, { useState, useMemo } from "react";
import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";
import BootstrapTable from "react-bootstrap-table-next";
import "react-bootstrap-table-next/dist/react-bootstrap-table2.min.css";
let tableData = [
{
rowsData: [
{
fname: "john",
lname: "smith"
},
{
fname: "steve",
lname: "warn"
},
{
fname: "michel",
lname: "clark"
}
],
columnsData: [
{
dataField: "fname",
text: "First name",
sort: true
},
{
dataField: "lname",
text: "last Name",
sort: true
}
]
}
];
export default function App() {
const [rowData, setrowData] = useState(tableData[0].rowsData);
const [rowSelect, setrowSelect] = useState([]);
const selectRow = useMemo(
() => ({
mode: "checkbox",
clickToSelect: false,
classes: "selection-row",
onSelect: (row, isSelect, rowIndex, e) => {
setrowSelect((rowData) =>
isSelect
? [...rowData, row]
: rowData.filter(
({ fname, lname }) => row.fname !== fname && row.lname !== lname
)
);
// if (isSelect === true) {
// setrowSelect((rowData) => [...rowData, row]);
// } else {
// console.log("onSelect", rowIndex, row, isSelect);
// setrowSelect((rowData) =>
// rowData.filter(
// ({ fname, lname }) => row.fname !== fname && row.lname !== lname
// )
// );
// }
},
onSelectAll: (isSelect, rows, e) => {
if (isSelect === true) {
setrowSelect(rows);
} else setrowSelect([]);
}
}),
[]
);
const Delete_device = () => {
console.log("Delete device", rowData, rowSelect);
if (rowSelect.length < 1) return;
setrowData((data) =>
data.filter(
({ fname, lname }) =>
rowSelect.find((s) => s.fname === fname && s.lname === lname) == null
)
);
setrowSelect([]);
};
console.log("App", rowData, rowSelect);
return (
<div className="App">
{rowData.length > 0 && (
<button className="btn btn-primary" onClick={Delete_device}>
<i className="ri-upload-cloud-line ri-lg"></i>Delete
</button>
)}
<BootstrapTable
bootstrap4
keyField="fname"
data={rowData}
columns={tableData[0].columnsData}
selectRow={selectRow}
/>
</div>
);
}
https://codesandbox.io/s/react-bootstrap-table-x-wus5r?file=/src/App.js
Related
For now, only apply:0 data are displayed by default in my table.I'm trying to send to my API when you click on Apply button, a list of all ids applied and not applied for instance : data:{apply: [3], notApply:[1,2]}.
Hereapply field in my data means the value is equal to 1 --> not displayed in the table; and notApply - i.e equal to 0 --> displaying data.
So when I click on the toggle button under the show column, it should turns to 1 the statusapply (not displaying the row data in my table which is not the case). The Cancel button sends nothing to my API.
Here what I've tried (but not working):
export default function MenuDisplayApi() {
const { menuId } = useParams();
const { match } = JsonData;
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
const [applyStatus, setApplyStatus]=useState(false)
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const [data, setData]= useState({
notApply:data.notApply,
apply: data.apply
})
function submit(e){
e.preventDefault()
axios.post(url,{
notApply:data.notApply,
apply: data.apply
})
.then(res => {
console.log(res)
})
}
// useEffect(() => {
// const data = {
// notApply:notApply,
// apply: apply
// }
// console.log('check', data)
// }, [])
// function handle(e){
// const newdata={...data}
// newdata[e.target.id]=e.target.value
// setData(newdata)
// console.log(newdata)
// }
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
return (
<div>
<button type="submit" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData && (applyStatus ? true : i.apply !== 1)}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
// ...other menus
{
"_id": 3,
"name": "Cucumber Soup ",
"description": "Cucumber Soup",
"dishes": [
{
"meat": "N/A",
"vegetables": "cucumber"
}
],
"taste": "Medium",
"comments": "2/4",
"price": "Medium",
"availability": 1,
"trust": 1,
"status": "Not started",
"apply": 0
}
]
Please check my codeSandbox. It works fine on local json files, so you can have a better understanding regarding what I'm trying.
Please see this picture for better understanding :
In my column Show there is a switch button (Toggle doesn't seems working in sandbox, maybe because of tailwindcss? but it works in local...) when you click on it, it will turn the selected row into gray (as if the row is disabled but you can still view the content).
We may have also the possibility to switch again and the original row (without gray) appears.
The VisibilityIcon button above the table will remove from all the table the gray/disabled rows (not working either). And a VisibilityoffIcon button that resets all (we get the original table).
Here what I have done but when I click on the Toggle I get errors and all the table is hidden:
export default function MenuDisplay() {
const { menuId } = useParams();
const { match } = JsonRules;
const dataFindings = match.find((el) => el._id_menu === menuId)?._ids ?? [];
const [disabled, setDisabled] = useState(false);
const toggler_disabled = () => {
disabled ? setDisabled(false) : setDisabled(true);
};
const data = useMemo(
() => [
//some headers ....
{
Header: 'Show',
accessor: (row) =>
<Toggle onClick ={toggler_disabled} value={disabled} onChange=
{setDisabled} />
}
],[]
);
...
return (
{
disabled?
<Table
data = { dataFindings }
columns = { data }
/>
: null
}
);
}
You're using useMemo under row data that memoize all rows which have the same click event without dependencies. If you want to call useMemo with an updated state, you can implement it this way
//`show` is your state
//`data` is your rows
useMemo(() => data, [show])
And the second problem is you track the show state which is only a true/false value. If you want to have multiple row states, you need to keep it as an array.
Here is the full code with some explanation (You also can check this playground)
import Table from "./Table";
import React, { useState, useMemo } from "react";
import JsonData from "./match.json";
import { useParams } from "react-router-dom";
import { Link } from "react-router-dom";
import VisibilityOffIcon from "#mui/icons-material/VisibilityOff";
import VisibilityIcon from "#mui/icons-material/Visibility";
export default function MenuDisplay() {
const { menuId } = useParams();
const { match } = JsonData;
const [hiddenRows, setHiddenRows] = useState([]);
const matchData = match.find((el) => el._id_menu === menuId)?._ids ?? [];
//update hidden row list
const updateHiddenRows = (rowId) => {
if (hiddenRows.includes(rowId)) {
//remove the current clicked row from the hidden row list
setHiddenRows(hiddenRows.filter((row) => row !== rowId));
} else {
//add the current clicked row from the hidden row list
setHiddenRows([...hiddenRows, rowId]);
}
};
const data = useMemo(() => [
{
Header: "Name",
accessor: (row) =>
//check current row is in hidden rows or not
!hiddenRows.includes(row._id) && (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>
{row.name}
</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => !hiddenRows.includes(row._id) && row.description
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => !hiddenRows.includes(row._id) && row.dishes,
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<button onClick={() => updateHiddenRows(row._id)}>
{!hiddenRows.includes(row._id) ? (
<VisibilityIcon>Show</VisibilityIcon>
) : (
<VisibilityOffIcon>Show</VisibilityOffIcon>
)}
</button>
)
}
], [hiddenRows]);
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" },
{ desc: false, id: "dishes" }
]
};
return (
<div>
<Table
data={matchData}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
/>
</div>
);
}
Keep a map of item ids that are selected and toggle these values via the Toggle component.
Use separate state for the toggle button to filter the selected items.
Implement a row props getter.
Example:
MenuDisplay
function MenuDisplay() {
const { menuId } = useParams();
const { match } = JsonData;
// toggle show/hide button
const [hideSelected, setHideSelected] = useState(false);
// select rows by item id
const [selected, setSelected] = useState({});
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const toggleShow = () => setHideSelected((hide) => !hide);
const matchData = (
match.find((el) => el._id_menu === menuId)?._ids ?? []
).filter(({ _id }) => {
if (hideSelected) {
return !selected[_id];
}
return true;
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
// add item id to row data
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
accessor: (row) => row.description
},
{
Header: "Dishes",
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"] // <-- hide id column too
};
return (
<div>
<button type="button" onClick={toggleShow}>
{hideSelected ? <VisibilityOffIcon /> : <VisibilityIcon />}
</button>
<Table
data={matchData}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps} // <-- pass rowProps getter
/>
</div>
);
}
Table
export default function Table({
className,
data,
columns,
initialState,
withCellBorder,
withRowBorder,
withSorting,
withPagination,
withColumnSelect,
rowProps = () => ({}) // <-- destructure row props getter
}) {
...
return (
<div className={className}>
...
<div className="....">
<table className="w-full" {...getTableProps()}>
<thead className="....">
...
</thead>
<tbody {...getTableBodyProps()}>
{(withPagination ? page : rows).map((row) => {
prepareRow(row);
return (
<tr
className={....}
{...row.getRowProps(rowProps(row))} // <-- call row props getter
>
...
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
I'm having an issue where upon loading the page I can either sort the table by the "name" column (ascending or descending) - - OR - - use a searchbar to filter through the names of the employees. My issue is that once I've sorted alphabetically, the search/filter no longer works.
I'm very new to React (I'm sure that's very obvious by my code) so please let me know if there's something obvious I'm doing wrong. Thanks in advance!
import React, { Component } from "react";
import API from "../utils/API"
import EmployeeRow from "./EmployeeRow"
class TableMain extends Component {
state = {
result: [],
search: "",
sortOrder: "descending"
}
componentDidMount() {
API.search()
.then(results => {
console.log(results)
this.setState({
result: results.data.results.map((res, i) => ({
image: res.picture.large,
firstName: res.name.first,
lastName: res.name.last,
phone: res.phone,
email: res.email,
dob: res.dob.date,
key: i
})
)
})
})
};
filterResults = (results) => {
const value = this.state.search
const finalResult = results.filter((employee) => {
const lastName = employee.lastName.toLowerCase();
const firstName = employee.firstName.toLowerCase()
const fullName = firstName + " " + lastName
if (fullName.includes(value)) {
return employee
}
});
return finalResult
};
sortResults = (event) => {
const results = this.state.result
// const id = event.target.id
// if (id === 'name'){
// } else if (id === 'phone'){
// } else if (id === 'email'){
// }
if (this.state.sortOrder === "descending") {
results.sort((a, b) => {
if (a.firstName > b.firstName) {
return -1
}
return a.firstName > b.firstName ? 1 : 0
},
this.setState({ sortOrder: "ascending" }))
} else if (this.state.sortOrder === "ascending") {
results.sort((a, b) => {
if (a.firstName < b.firstName) {
return -1
}
return a.firstName > b.firstName ? 1 : 0
},
this.setState({ sortOrder: "descending" }))
}
console.log("RESULTS: ", results)
this.setState({
sortedResults: results,
isSorted: true
})
}
onChange = e => {
const value = e.target.value;
if (!value) {
this.setState({ isSearchEmpty: true });
} else {
this.setState({ search: e.target.value, isSearchEmpty: false });
}
}
render() {
// console.log("State", this.state)
let employeeResults = this.state.result
if (this.state.isSearchEmpty) {
employeeResults = this.state.result
} else {
employeeResults = this.filterResults(this.state.result)
}
if (this.state.isSorted) {
employeeResults = this.state.sortedResults
}
return (
<div>
<input label="Search" onChange={this.onChange} />
<div className="row">
<table style={{ width: "100%" }}>
<tbody>
<tr>
<th>Image</th>
<th style={{ cursor: "pointer" }} onClick={this.sortResults} id="name">Name</th>
<th id="phone">Phone</th>
<th id="email">Email</th>
<th id="dob">DOB</th>
</tr>
{[...employeeResults].map((item) =>
<EmployeeRow
image={item.image}
firstName={item.firstName}
lastName={item.lastName}
email={item.email}
phone={item.phone}
dob={item.dob}
key={item.key}
/>
)}
</tbody>
</table>
</div>
</div>
)}
}
export default TableMain;
The issue is:
if (this.state.isSorted) {
employeeResults = this.state.sortedResults;
}
When you sort, you set state.isSorted to true, however you never set it back to false once you have finished. When you then try to filter, you do the filter:
if (this.state.isSearchEmpty) {
employeeResults = this.state.result;
} else {
employeeResults = this.filterResults(this.state.result);
}
if (this.state.isSorted) { // this is never reset after sorting.
employeeResults = this.state.sortedResults;
}
But as this.state.isSorted is still true, you use the values in this.state.sortedResults again.
please let me know if there's something obvious I'm doing wrong
You are making this tricky for yourself, as you are filtering/sorting the same collection of data. That's why you need to perform the action in the render, as you are trying to maintain the original list for later usage.
If you seperate the list into two collections: original unmodified and a display list, you can always refer to the original list to perform filtering/sorting.
componentDidMount() {
API.search().then(results => {
const tableData = results.data.results.map((res, i) => ({
image: res.picture.large,
firstName: res.name.first,
lastName: res.name.last,
phone: res.phone,
email: res.email,
dob: res.dob.date,
key: i
}));
this.setState({ originalResults: tableData, displayResults: tableData });
});
}
Then filtering can be done, as soon as the onChange occurs:
onChange = e => {
const query = e.target.value;
this.setState(prevState => ({
displayResults:
query.length > 0
? this.filterResults(query, prevState.originalResults)
: prevState.originalResults
}));
};
and similarly for the sorting, which can be performed on the display-results rather than the whole which means you can now sort, filtered results.
I created an example here https://codesandbox.io/s/sad-cannon-d61z6
I stubbed out all the missing functionality.
i want to move a list item into view using scrollIntoView method using reactjs.
What i am trying to do?
i have an array of objects stored in variable some_arr and i display those values in a dropdown menu. when user presses down key then the dropdown item gets highlighted. also using up arrow key to navigate up in the dropdown menu.
and clicking enter key will select the dropdown item and replaces the value in the input field.
I have implemented code below and it works fine. but when user presses down arrow key and highlighted dropdown menu is not in view i want it to be visible to user.
So to implement that i have used ref (this.dropdown_item_ref) to the dropdown item. however this ref always points to last item in the dropdown menu. meaning consider i have
some_arr = [
{
id:1,
name: somename,
},
{
id: 2,
name: fname,
},
{
id: 3,
name: lname, //ref is always pointing to this item
},
],
so here the ref is always pointing to lname in the dropdown menu.
Below is what i have tried and is not working,
class Dropdownwithinput extends React,PureComponent {
constructor(props) {
super(props);
this.list_item_ref = React.createRef();
this.state = {
input_val: '',
dropdown_values: [],
dropdown_item_selection: 0,
};
}
componentDidMount = () => {
const values = [
{
id:1,
name: somename,
},
{
id: 2,
name: fname,
},
{
id: 3,
name: lname, //ref is always pointing to this item
},
],
this.setState({dropdown_values: values});
}
handle_key_down = (event) => {
if (this.state.dropdown_values > 0) {
if (event.keyCode === 38 && this.state.dropdown_item_selection
> 0) {
this.setState({dropdown_item_selection:
(this.state.dropdown_item_selection - 1) %
this.state.dropdown_values.length});
this.list_item_ref.current.scrollIntoView();
} else if (event.keyCode === 40) {
this.setState({dropdown_item_selection:
(this.state.dropdown_values_selection + 1) %
this.state.dropdown_values.length});
this.list_item_ref.current.scrollIntoView();
}
if (event.keyCode === 13) {
event.preventDefault();
const selected_item =
this.state.dropdown_values[this.state.user_selection];
const text = this.replace(this.state.input_val,
selected_item);
this.setState({
input_val: text,
dropdown_values: [],
});
}
}
replace = (input_val, selected_item) => {
//some function to replace value in input field with the
//selected dropdown item
}
render = () => {
return (
<input
onChange={this.handle_input_change}
onKeyDown={this.handle_key_down}/>
<div>
{this.state.dropdown_values.map((item, index) => (
<div key={index} className={"item" + (index ===
this.state.dropdown_item_selection ? ' highlight'
: '')}>
{item.name}
</div>
))}
</div>
)
};
}
}
Could someone help me fix this. thanks.
I have adapted a bit your code:
import React from "react";
class Example extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
const dropdownValues = Array.from({ length: 100 }, (_, k) => k).reduce(
(acc, curr) => {
return acc.concat([{ id: curr, name: `${curr}.so` }]);
},
[]
);
this.state = {
input_val: "",
dropdownValues,
selectedItem: 0
};
this.listRefs = dropdownValues.reduce((acc, current, index) => {
acc[index] = React.createRef();
return acc;
}, {});
}
componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown);
}
componentWillUnmount() {
window.removeEventListener("keydown", this.handleKeyDown);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.selectedItem !== this.state.selectedItem) {
this.listRefs[this.state.selectedItem].current.scrollIntoView();
}
}
handleKeyDown = event => {
const keyCodes = {
up: 38,
down: 40
};
if (![38, 40].includes(event.keyCode)) {
return;
}
this.setState(prevState => {
const { dropdownValues, selectedItem } = prevState;
let nextSelectedItem;
if (keyCodes.up === event.keyCode) {
nextSelectedItem =
dropdownValues.length - 1 === selectedItem ? 0 : selectedItem + 1;
}
nextSelectedItem =
selectedItem === 0 ? dropdownValues.length - 1 : selectedItem - 1;
return { ...prevState, selectedItem: nextSelectedItem };
});
};
replace = (input_val, selected_item) => {
//some function to replace value in input field with the
//selected dropdown item
};
render() {
return (
<>
<input
onChange={this.handle_input_change}
onKeyDown={this.handle_key_down}
/>
<button
type="button"
onClick={() => this.setState({ selectedItem: 50 })}
>
Focus element 50
</button>
<div ref={this.listRef}>
{this.state.dropdownValues.map((item, index) => (
<div key={index} ref={this.listRefs[index]}>
<div
style={
this.state.selectedItem === index
? { background: "yellow" }
: {}
}
>
{item.name}
</div>
</div>
))}
</div>
</>
);
}
}
export default Example;
I did a CRUD (UI only) simple component in react, but how do I make the primary contact to be the first one in my table? The app can do crud, check and uncheck primary contact, only one primary contact is allowed in the table.
Working demo
https://codesandbox.io/s/r7kmp9rkom
================================================
I've tried using lodash's sortBy
(Broken demo using sortBy
https://codesandbox.io/s/pjj3098lmx)
line 130
<tbody>
{sortBy(contacts, o => !o.primary).map((o, i) => {
return (
<tr className={classNames({ primary: o.primary })} key={i}>
<td>{o.name}</td>
<td>{o.email}</td>
<td>
<button
onClick={() =>
this.setState({
openModal: true,
modalAction: "update",
selected_contact: o,
selected_contact_index: i
})
}
>
Edit
</button>
</td>
</tr>
);
})}
</tbody>
But it broke the functionality. I think it has to do with the index problem.
I couldn't solve it I don't know why sortBy doesn't retain the index. Another silly option would be using flexbox order but I hope I could solve it using just javascript.
As you told it is an index problem
I have used lodash's uniqueId method to retain id value, never use an index as key for a dynamic list when we have operations like deleting/updating/adding.
if an app is a server-side render id must come from a backend.
id: uniqueId("contact_")
Working Demo link
static defaultProps = {
data: {
contacts: [
{
name: "James",
email: "james#havard.edu",
primary: false,
id: uniqueId("contact_")
},
{
name: "Mark",
email: "mark#standford.edu",
primary: true,
id: uniqueId("contact_")
}
]
}
};
onSaveContact = (action, newContact, newContact_index) => {
if (action === "add") {
if (newContact.primary) {
const setNonePrimary = this.state.contacts.map(o => ({
...o,
primary: false
}));
this.setState({
contacts: [
...setNonePrimary,
{ ...newContact, id: uniqueId("contact_") }
]
});
} else {
this.setState({
contacts: [
...this.state.contacts,
{ ...newContact, id: uniqueId("contact_") }
]
});
}
} else if (action === "update") {
this.setState({
contacts: [
...this.state.contacts.map((o, i) => {
if (o.id === newContact_index) {
return { ...newContact };
} else {
if (newContact.primary) {
return { ...o, primary: false };
} else {
return { ...o };
}
}
})
]
});
} else if (action === "delete") {
this.setState({
contacts: [
...this.state.contacts.filter((o, i) => {
if (o.id !== newContact_index) {
return o;
}
})
]
});
}