Check if Contains in Array in React - javascript

I wanted to hide menus conditionally based on userAccess. My problem is if has many roles.
How will I be able to do it?
Pls check my code below
MENU
export default [
{
id: 1,
text: 'Leaders',
url: `/leaders`,
userAccess: true,
},
{
id: 2,
text: 'Things',
url: `/things`,
userAccess: 'Super Admin',
},
{
id: 3,
text: 'Users',
url: `/users`,
userAccess: 'Admin',
},
{
id: 4,
text: 'Products',
url: `/products`,
userAccess: ['Admin', 'Manager'],
},
];
CODE
import { useSelector } from 'react-redux';
const checkAccess = (access, role) => {
if (typeof access === 'string') {
return role === access;
};
return access;
};
const Menu = ({ items, isCollapseMenu }) => {
const classes = useStyles();
const role = useSelector((state) => state.role);
return (
<div className={classes.root}>
{items.map(
(item) =>
checkAccess(item.userAccess, role) && (
<MenuItem
key={item.id}
title={item.title}
icon={item.icon}
text={item.text}
shortText={item.shortText}
url={item.url}
submenu={item.submenu}
isCollapseMenu={isCollapseMenu}
/>
)
)}
</div>
);
};

You need to use includes method to check access and also try to filter array before doing map like below:-
import { useSelector } from 'react-redux';
const checkAccess = (access, role) => {
if (typeof access === 'string') {
return role === access;
};
if(Array.isArray(access)){
return access.includes(role);
}
return access;
};
const Menu = ({ items, isCollapseMenu }) => {
const classes = useStyles();
const role = useSelector((state) => state.role);
return (
<div className={classes.root}>
{items.filter(itemToFilter => checkAccess(itemToFilter.userAccess, role)).map(
(item) => (
<MenuItem
key={item.id}
title={item.title}
icon={item.icon}
text={item.text}
shortText={item.shortText}
url={item.url}
submenu={item.submenu}
isCollapseMenu={isCollapseMenu}
/>
)
)}
</div>
);
};

Try to re-write checkAccess function as follows
and make sure your roles are always in an array either it contains single value or multiple values
const checkAccess = (access, role) => {
if (typeof access === 'string') {
return role.indexOf(access) > -1;
};
return access;
};

You have to handle 3 conditions,
String.
Array
boolean
const checkAccess = (access, role) => {
if (typeof access === 'string') { // String check
return role === access;
};
if(Array.isArray(access)){ //Array check
return access.includes(role);
}
return access; // Boolean check
};

Related

Show and Hide Condition in React

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.

How to update state in onSubmit form & send data through components?

I have a state which I need to update with the ID returned from an endpoint call so I can call another another endpoint using that ID, I've made a state in the parent component and I use it in my first form to set the ID. I pass that id as a prop to the component that needs it but when I console.log the state, it doesn't change.
How can I pass the ID through the components?
I've added comments on the main places to look at
Here is my first form where I need the ID from -
const AddWebAppTypeForm = (props: any, ref: any) => {
const { setWebAppTypes, setNewAppValues}: AddWebAppTypeFormProps =
props;
const {
handleSubmit,
control,
reset,
formState: { isDirty },
} = useForm();
const onSubmit = (data: any) => {
let formData = new FormData();
formData.append("name", data.Title);
formData.append("description", data.Description);
formData.append("uploaded_file", data.Image[0]);
if (isDirty) {
createWebAppType(formData);
}
};
const createWebAppType = async (body: any) => {
await fetch(`${process.env.REACT_APP_API_URL}/webapptype`, {
method: "POST",
body: body,
})
.then((response) => response.json())
.then((data: IWebAppType) => {
const model: IWebAppType = {
id: data.id,
name: data.name,
image: data.image,
description: data.description,
image_url: data.image_url,
};
setNewAppValues(model.id); // <--- Set the state here
setWebAppTypes((prev) =>
prev.map((item) => (item.id === 0 ? model : item))
);
enqueueSnackbar(`Created App Succesfully`, {
variant: "success",
});
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<button hidden={true} ref={ref} type="submit" />
</form>
);
};
export default forwardRef(AddWebAppTypeForm);
My parent component with the states -
function WebAppTypeAccordion({ a, setWebAppTypes }: WebAppTypeAccordionProps) {
const [formEl, setFormEl] = useState(null);
const [addFormEl, setAddFormEl] = useState(null);
const [newAppValues, setNewAppValues] = useState<number>(0); // <--- state with 0 as initialised value
const handleRef = (el: any) => {
if (el !== null) {
setFormEl(el);
}
};
const handleAddRef = (el: any) => {
if (el !== null) {
setAddFormEl(el);
}
};
return (
<Accordion defaultExpanded={a.id === 0}>
<AccordionSummary
// onClick={(e) => handleOnClick(a, e)}
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>{a.name}</Typography>
</AccordionSummary>
<AccordionDetails>
{a.id === 0 ? (
<AddWebAppTypeForm
setWebAppTypes={setWebAppTypes}
ref={handleAddRef}
setNewAppValues={setNewAppValues} // <--- Passing setState to set id
/>
) : (
null
)}
<MappedAccordion
waobj={a}
key={a.id}
setWebAppTypes={setWebAppTypes}
editRef={formEl}
addRef={addFormEl}
newAppValues={newAppValues} // <--- Passing ID
/>
</AccordionDetails>
</Accordion>
);
}
export default WebAppTypeAccordion;
Here is where I am trying to use the ID to call another endpoint
function MappedAccordion({
waobj,
setWebAppTypes,
editRef,
addRef,
newAppValues,
}: MappedAccordionProps) {
const handleCreate = (data: FieldT) => {
let wtype: string = String(waobj.id);
if (addRef !== null) {
if (newAppValues !== 0) {
wtype = String(newAppValues); // <--- Try to use the ID but get default value
createFetch(data, wtype); // <--- Try to use the ID but get default value
}
}
createFetch(data, wtype);
};
const createFetch = (data: FieldT, wtype: string) => {
let formData = new FormData();
formData.append("name", data.name);
formData.append("link", data.link);
formData.append("wtype", wtype);
fetch(`${process.env.REACT_APP_API_URL}/webapp/`, {
method: "POST",
body: formData,
})
.then((response) => {
if (!response.ok) {
let err = new Error("HTTP status code: " + response.status);
enqueueSnackbar(`Environment already exists`, {
variant: "error",
});
throw err;
}
return response.json();
})
.then((data: IWebApp) => {
const model: FieldT = {
wid: data.id,
name: data.name,
link: data.link,
};
enqueueSnackbar(`Created Environment ${model.wid}`, {
variant: "success",
});
});
};
const onSubmit = (data: FormFields) => {
if (addRef !== null) addRef?.click(); // <--- Submit AddWebAppTypeForm form, set the ID
else editRef?.click();
let onSubmitData: FieldT[] = data.myFieldValues;
onSubmitData.map((a: FieldT, index) => {
let originalField: FieldT = initialFields[index];
if (a.wid === undefined) {
handleCreate(a);
} else {
if (JSON.stringify(a) !== JSON.stringify(originalField)) {
handleEdit(a);
}
}
});
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} id="environment-form">
<div style={{ paddingTop: 10 }}>
<Button
type="submit" // <--- Submit form
variant="outlined"
size="small"
sx={{ marginRight: 1 }}
>
Save
</Button>
<Button
variant="outlined"
size="small"
onClick={handleAppDelete}
disabled={waobj.id === 0 ? true : false}
>
Delete
</Button>
</div>
</form>
</div>
);
}
export default MappedAccordion;
Thanks for taking a look, I appreciate any help!

TypeError: Cannot read properties of undefined (reading 'url')

I'm working on building a drag and drop card game. I'm not familiar with using the library react-dnd I wonder if this has something to do with it. If I use data already on the file, it works fine, but if I have to fetch the data, it creates This error.
As I said, this usually happens when I use useEffect Somebody has a better idea of how to do this, please let me know.
import React, { useEffect, useState } from 'react';
import Card from './Card';
import { useDrop } from 'react-dnd';
import './Table.css';
const API_Data = [
{
id: 1,
url: 'https://deckofcardsapi.com/static/img/6H.png',
},
{
id: 2,
url: 'https://deckofcardsapi.com/static/img/aceDiamonds.png',
},
{
id: 3,
url: 'https://deckofcardsapi.com/static/img/7C.png',
},
{
id: 4,
url: 'https://deckofcardsapi.com/static/img/6H.png',
},
{
id: 5,
url: 'https://deckofcardsapi.com/static/img/aceDiamonds.png',
},
{
id: 6,
url: 'https://deckofcardsapi.com/static/img/7C.png',
},
];
function Table() {
const [playersCard, setPlayersCard] = useState([]);
const [potA, setPotA] = useState([
{
id: 1,
url: 'https://deckofcardsapi.com/static/img/6H.png',
},
]);
const [potB, setPotB] = useState([]);
/////////////////////////////////////////////////////////////////////////
const [, dropA] = useDrop(() => ({
accept: 'card',
drop: (item) => handleAddToPotA(item.id),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const handleAddToPotA = (id) => {
const newCard = playersCard.filter((card) => id === card.id);
console.log(`newCard`, newCard);
setPotA((oldCard) => [...oldCard, newCard[0]]);
};
//////////////////////////////////////////////////////////////////////////
const [, dropB] = useDrop(() => ({
accept: 'card',
drop: (item) => handleAddToPotB(item.id),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const handleAddToPotB = (id) => {
const newCard = playersCard.filter((card) => id === card.id);
setPotB((oldCard) => [...oldCard, newCard[0]]);
console.log(newCard);
};
useEffect(() => {
setPlayersCard(API_Data);
return () => {};
}, []);
//////////////////////////////////////////////////////////////////////////
if (!playersCard) {
return <div></div>;
}
return (
<div className="table-page">
<div className="center-table">
<div className="pot-a" ref={dropA}>
{potA &&
potA.map((card) => {
return <Card url={card?.url} id={card.id} key={card.id} />;
})}
</div>
<div className="pot-b" ref={dropB}>
{potB &&
potB.map((card) => {
return <Card url={card.url} id={card.id} key={card.id} />;
})}
</div>
</div>
<div className="players-card">
{playersCard.map((card) => {
return <Card url={card.url} id={card.id} key={card.id} />;
})}
</div>
</div>
);
}
export default Table;
Because playersCard is not initialized yet.
try to check undefined first
<div className="players-card">
{playersCard.length > 0 && playersCard.map((card) => {
return <Card url={card.url} id={card.id} key={card.id} />;
})}
</div>
A Possible Reason:
Check your function and method names for typos

Keep track of changes in React state

I have a provider that receives data prop, puts it in a state. Also, there are a few methods to manipulate that state.
I pass the state and the data prop to consumers, but every time I change the state, there is no difference between the prop and the state. I want to be able to see what changed so I could update that value.
import { createContext, useContext, useEffect, useState } from "react";
const TableContext = createContext({
data: [],
headings: [],
onChangeCellContent: () => {},
});
const TableProvider = ({ data, headings, children }) => {
const [tableData, setData] = useState(data);
const [tableHeadings, setHeadings] = useState(headings);
useEffect(() => {
setData((previousData) => {
return data.length !== previousData.length ? data : previousData;
});
}, [data]);
const onChangeHeadingCell = ({ key, value }) => {
setHeadings((oldHeadings) =>
oldHeadings.map((heading) => {
if (heading.key === key) {
heading.title = value;
}
return heading;
})
);
};
const onChangeCellContent = ({ rowId, cellKey, value }) => {
setData((previousData) =>
[...previousData].map((row) => {
if (row.id === rowId) {
row[cellKey] = value;
return row;
}
return row;
})
);
};
const onAddNewRow = (rowData) => {
setData((oldData) => [...oldData, rowData]);
};
return (
<TableContext.Provider
value={{
tableData,
data,
onChangeCellContent,
onChangeHeadingCell,
onAddNewRow,
headings: tableHeadings,
}}
>
{children}
</TableContext.Provider>
);
};
export default TableProvider;
export const useTable = () => {
const context = useContext(TableContext);
if (context === "undefined") {
throw Error("Table provider missing");
}
return context;
};
Here is the change handler, it works, but it also changes the original data:
const Row = ({ data: row}) => {
const { onChangeCellContent, headings, data } = useTable();
...
// GIVES ME THE SAME VALUE WHEN I TRIGGER ONCHANGE
console.log(row.value, data.find((s) => s.id === row.id).value);
return <tr><td><select
className="w-full h-full focus:outline-none"
style={{
backgroundColor: "inherit",
}}
value={row.value}
onChange={(e) =>
onChangeCellContent({
rowId: row.id,
cellKey: "value",
value: e.target.value,
})
}
>...</select></td></tr>

react search filter with select option

i have implemented search filter to my react app, everything worked fine till i made select tag, to change search filter criteria.
class BookList extends Component {
state = {
search: '',
selectedValue: 'name',
options: [
{
name: 'Name',
value: 'name',
},
{
name: 'Author',
value: 'author',
},
{
name: 'ISBN',
value: 'isbn',
}
]
}
updateSearch (e) {
this.setState({search: e.target.value});
}
selectedValueHandler (e) {
this.setState({selectedValue: e.target.value});
}
render () {
if (this.state.selectedValue === 'name') {
let filteredBooks = this.props.books.filter(book => {
return book.name.toLowerCase().indexOf(this.state.search) !== -1;
})
} else if (this.state.selectedValue === 'author') {
let filteredBooks = this.props.books.filter(book => {
return book.author.toLowerCase().indexOf(this.state.search) !==
-1;
})
} else if (this.state.selectedValue === 'isbn') {
let filteredBooks = this.props.books.filter(book => {
return book.isbn.indexOf(this.state.search) !== -1;
})
}
return (
<div>
<div className='SearchInput'>
<input type='text'
value={this.state.search}
onChange={this.updateSearch.bind(this)} />
<select
id="searchSelect"
name="searchSelect"
onChange={this.selectedValueHandler.bind(this)} >
{this.state.options.map(item => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</select>
</div>
<div className='BookList'>
<ul>
{filteredBooks.map(book => {
return <Book key={book.book_id} name={book.name} author={book.author} isbn={book.isbn} />
})}
</ul>
</div>
</div>
)
}
};
export default BookList;
when i implement this code i am getting error: Line 69: 'filteredBooks' is not defined no-undef.
Tried to put this.state.selectedValue instead of name but it also doesn't work.
Any ideas how to fix issue?
let variables are locally scoped to the nearest wrapping curly braces. Define the variable above the if statements.
render () {
let filteredBooks;
if (this.state.selectedValue === 'name') {
filteredBooks = this.props.books.filter(book => {
return book.name.toLowerCase().indexOf(this.state.search) !== -1;
})
...
Unrelated, here's one way you could shorten your code:
const { books } = this.props;
const { search } = this.state;
const filteredBooks = books.filter(book =>
book[search].toLowerCase().includes(search)
)

Categories