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 :
Related
I have a simple problem here which I can't figure out. I wanted to hide menus depending on the condition.
For example if status contains at least one "Unlinked". "All unlinked images" menu should appear. I did used .some and I wonder why it doesn't return a boolean.
Codesandbox is here Click here
const showDeleteAllInvalidButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Invalid");
};
const showDeleteAllUnlinkedButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Unlinked");
};
The methods do return a boolean. But in the menus array you are assigning a function reference not the result -
show: showDeleteAllInvalidButton // function reference
show is now assigned a reference to the function showDeleteAllInvalidButton not the result of productImages?.some. You need to invoke the functions when assigning -
show: showDeleteAllInvalidButton() // result of productImages?.some
In your menus object you have a key that contains a function, so if you want this function to filter out your elements you need to execute the show method in side the filter method.
import React, { useState } from "react";
import Button from "#mui/material/Button";
import MenuItem from "#mui/material/MenuItem";
import KeyboardArrowDownIcon from "#mui/icons-material/KeyboardArrowDown";
import CustomMenu from "../../Menu";
const products = [
{
productName: "Apple",
productImages: [
{
status: "Unlinked"
}
]
},
{
productName: "Banana",
productImages: [
{
status: "Unlinked"
}
]
},
{
productName: "Mango",
productImages: [
{
status: "Unlinked"
},
{
status: "Unlinked"
}
]
}
];
const HeaderButtons = () => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const showDeleteAllInvalidButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Invalid");
};
const showDeleteAllUnlinkedButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Unlinked");
};
const menus = [
{
id: 1,
name: "Invalid images",
action: () => {
handleClose();
},
show: showDeleteAllInvalidButton
},
{
id: 2,
name: "Unlinked images",
action: () => {
handleClose();
},
show: showDeleteAllUnlinkedButton
},
{
id: 3,
name: "All images",
action: () => {
handleClose();
},
show: () => true // not that I changed it to a function for consistency, but you can check for type in the filter method instead of running afunction
}
];
return (
<div>
<Button
color="error"
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
variant="outlined"
onClick={handleClick}
endIcon={<KeyboardArrowDownIcon />}
>
Options
</Button>
<CustomMenu anchorEl={anchorEl} open={open} onClose={handleClose}>
{menus
.filter((e) => e.show()) // here is your mistake
.map(
({
id = "",
action = () => {},
icon = null,
name = "",
divider = null
}) => (
<>
<MenuItem key={id} onClick={action} disableRipple>
{icon}
{name}
</MenuItem>
{divider}
</>
)
)}
</CustomMenu>
</div>
);
};
export default HeaderButtons;
In your code, it will always render because your filter functions are evaluating as truth.
I need to set the active classname to multiple onclick items inside a .map
I need the list of active items that were clicked
The items that were clicked will be highlighted in yellow, and when i click the same item again it should be removed from active list items.
const [data, setData] = useState([]);
const [activeIndicies, setActiveIndicies] = useState(() =>
data?.map(() => false)
);
useEffect(() => {
// This data is coming from the API response
const data = [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" }
];
setData(data);
}, []);
return statement
onClick={() => {
setActiveIndicies(
activeIndicies.map((bool, j) => (j === index ? true : bool))
);
}}
Code Sandbox
Thank you.
try this one:
import "./styles.css";
import React, { useState, useEffect } from "react";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "one", active: false },
{ id: 2, name: "two", active: false },
{ id: 3, name: "three", active: false }
]);
return (
<div className="App">
<h2>Set active className to multiple items on .map</h2>
{data?.map((item, index) => {
return (
<p className={data[index].active ? "selected" : "notselected"}
onClick={() => {
setData((prevState) =>
_.orderBy(
[
...prevState.filter((row) => row.id !== item.id),
{ ...item, active: !item.active }
],
["name"],
["asc"]
)
);
}}
>
{item.name}
</p>
);
})}
</div>
);
}
You can acheive this by simply making some minor changes to your code:
// Changing the state value to an object so that it can
// store the active value for exact item ids
const [activeIndicies, setActiveIndicies] = useState({});
Then inside of .map()
....
// Checking if there is any value for the item id which is being mapped right now.
const selected = activeIndicies[item.id];
return (
<p
className={selected ? "selected" : "notselected"}
onClick={() => {
/* Then setting the state like below where it toggles
the value for particular item id. This way if item is
selected it will be deselected and vice-versa.
*/
setActiveIndicies((prevState) => {
const newStateValue = !prevState[item.id];
return { ...prevState, [item.id]: newStateValue };
});
}}
// Key is important :)
key={item.id}
>
{item.name}
</p>
);
Hello, friends!
I solved this problem in a more convenient way for me )
const data = [
{ id: 1, name: "Ann", selected: true },
{ id: 2, name: "Serg", selected: false },
{ id: 3, name: "Boris", selected: true },
];
//you don't even need to have a boolean field in the object -
//it will be added by itself on the first click on the element
// const data = [{ name:"Ann", id:1}, { name:"Serg", id:2 },{ name:"Boris", id:3 },]
const [users, setUsers] = useState(data); // no square brackets-[data]
function handleActive(item) {
setUsers((prev) => {
return prev.map((itemName) => {
if (itemName.name === item.name) {
return { ...itemName, selected: !itemName.selected };
// itemName.selected = !itemName.selected // or so
}
return itemName;
});
});
}
return (
{
users.map((item, i) => {
// let current = item.selected === true
// or so -> className={current === true ? 'users': ''}
return (
<div
onClick={() => handleActive(item)}
className={item.selected === true ? "active" : ""}
key={i}
>
{item.name}
</div>
);
})
}
);
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>
);
}
First time poster so let me know if more information is need.
Trying to figure out why my global state using context API is being updated even when my setSate method is commented out. I thought i might have been mutating the state directly accidently but I dont believe I am
"specialModes" in actionOnClick() is the state in question
const SpecialFunctions: FC = (props: Props) => {
const { currentModeContext, specialModesContext: specialActionsContext, performCalc, inputValueContext } = useContext(AppContext)
const { specialModes, setSpecialModes } = specialActionsContext
const { currentMode, setCurrentMode } = currentModeContext
const { inputValue, setInputValue } = inputValueContext
const categoryOnClick = (index: number) => {
setCurrentMode(specialModes[index])
console.log(specialModes[index].title);
}
const actionOnClick = (action: IAction) => {
let newAction = action
newAction.value = performCalc()
let newSpecialModes = specialModes.map((mode) => {
if (mode === currentMode) {
let newMode = mode
newMode.actions = mode.actions.map((element) => {
if (element === action) {
return newAction
}
else return element
})
return newMode
}
else return mode
})
//setSpecialModes(newSpecialModes)
}
let headings = specialModes.map((categorgy, index) => {
return <Heading isActive={categorgy === currentMode ? true : false} onClick={() => categoryOnClick(index)} key={index}>{categorgy.title}</Heading>
})
let actions = currentMode.actions.map((action, index) => {
return (
<Action key={index} onClick={() => actionOnClick(action)}>
<ActionTitle>{action.title}</ActionTitle>
<ActionValue>{action.value}</ActionValue>
</Action>
)
})
return (
<Wrapper>
<Category>
{headings}
</Category>
<ActionsWrapper toggleRadiusCorner={currentMode === specialModes[0] ? false : true}>
{actions}
</ActionsWrapper>
</Wrapper>
)
}
Context.tsx
interface ContextType {
specialModesContext: {
specialModes: Array<ISpecialModes>,
setSpecialModes: React.Dispatch<React.SetStateAction<ISpecialModes[]>>
},
currentModeContext: {
currentMode: ISpecialModes,
setCurrentMode: React.Dispatch<React.SetStateAction<ISpecialModes>>
},
inputValueContext: {
inputValue: string,
setInputValue: React.Dispatch<React.SetStateAction<string>>
},
inputSuperscriptValueContext: {
inputSuperscriptValue: string,
setInputSuperscriptValue: React.Dispatch<React.SetStateAction<string>>
},
performCalc: () => string
}
export const AppContext = createContext({} as ContextType);
export const ContextProvider: FC = ({ children }) => {
const [SpecialModes, setSpecialModes] = useState([
{
title: 'Rafter',
actions: [
{
title: 'Span',
active: false
},
{
title: 'Ridge Thickness',
active: false
},
{
title: 'Pitch',
active: false
}
],
},
{
title: 'General',
actions: [
{
title: 'General1',
active: false
},
{
title: 'General2',
active: false
},
{
title: 'General3',
active: false
}
],
},
{
title: 'Stairs',
actions: [
{
title: 'Stairs1',
active: false
},
{
title: 'Stairs2',
active: false
},
{
title: 'Stairs3',
active: false
}
],
}
] as Array<ISpecialModes>)
const [currentMode, setCurrentMode] = useState(SpecialModes[0])
const [inputValue, setInputValue] = useState('0')
const [inputSuperscriptValue, setInputSuperscriptValue] = useState('')
const replaceCharsWithOperators = (string: string): string => {
let newString = string.replaceAll(/\s/g, '') // delete white space
newString = newString.replace('×', '*')
newString = newString.replace('÷', '/')
console.log(string)
console.log(newString)
return newString
}
const performCalc = (): string => {
let originalEquation = `${inputSuperscriptValue} ${inputValue} =`
let equation = inputSuperscriptValue + inputValue
let result = ''
equation = replaceCharsWithOperators(equation)
result = eval(equation).toString()
setInputSuperscriptValue(`${originalEquation} ${result}`)
setInputValue(result)
console.log(result)
return result
}
return (
<AppContext.Provider value={
{
specialModesContext: {
specialModes: SpecialModes,
setSpecialModes: setSpecialModes
},
currentModeContext: {
currentMode,
setCurrentMode
},
inputValueContext: {
inputValue,
setInputValue
},
inputSuperscriptValueContext: {
inputSuperscriptValue,
setInputSuperscriptValue
},
performCalc
}}>
{children}
</AppContext.Provider>
)
}
In your mode.actions.map() function you are indirectly changing actions field of your original specialModes array.
To fix this problem you need to create shallow copy of specialModes array Using the ... ES6 spread operator.
const clonedSpecialModes = [...specialModes];
let newSpecialModes = clonedSpecialModes.map((mode) => {
// rest of your logic here
})
I try to implement global search with new material ui next table component, I have handleSearch method, wich recives event, and than I use regexp to check if event.target.value the same as in table. But when I delete string in search, the columns not update. It starts to search only I start type. How to search by number not only by string in this case
const columns = [
{
dataKey: 'deviceType',
label:'Device Type',
numeric: false,
}, {
dataKey: 'deviceID',
label:'Device ID',
sortable: true,
numeric: true,
// cellRenderer: ({item, key}) =>
// <Button >Default</Button>,
}, {
........
}]
const data = [
{ key: 1, deviceType: 'Tag', deviceID: 1, name:'Tag For sending an ', location: 'Room_104', status: 'assigned'},
{ key: 2, deviceType: 'Tag', deviceID: 2, name:'Tag For sending an ', location: 'Room_104', status: 'assigned'},
{.......},
]
class EnhancedTable extends Component {
state = {
selected: [],
data,
order: {
direction: 'asc',
by: 'deviceID',
},
search: '',
}
handleSearch = event => {
debugger
const {data} = this.state
let filteredDatas = []
filteredDatas = data.filter(e => {
let mathedItems = Object.values(e)
let returnedItems
mathedItems.forEach(e => {
const regex = new RegExp(event.target.value, 'gi')
if (typeof e == 'string')
returnedItems = e.match(regex)
})
return returnedItems
})
this.setState({data: filteredDatas, search: event.target.value})
}
render = () => {
const {data, search} = this.state
return (
<Paper>
<Table
data={data}
search={search}
onSearch={this.handleSearch}
/>
</Paper>)
}
}
export default EnhancedTable
fuzzyContains = (text, search) => {
debugger
if (!text)
return false
if (!search)
return true
search = search.toLowerCase()
text = text.toString().toLowerCase()
let previousLetterPosition = -1
return search.split('').every(s => {
debugger
previousLetterPosition = text.indexOf(s, previousLetterPosition + 1)
return previousLetterPosition !== -1
})
}
handleSearch = search => {
const {data} = this.state
// debugger
let filteredData = data.filter(x => Object.keys(x).some(key =>
// debugger
this.fuzzyContains(x[key], search)
))
console.log(filteredData)
this.setState({filteredData, search})
}