Popover with id data in list (React, Material UI) - reactjs

I have a table list with first name, last name and email. The email section is a clickable popover SimplePopper:
<TableCell>{list.first_name}</TableCell>
<TableCell>{list.last_name}</TableCell>
<TableCell><SimplePopper /></TableCell>
I'd like the popover to only display the email related to the id at the row level, but at the moment it displays all emails. Is there a simple way to pass something on <SimplePopper /> so I see the related emails to each first and last name?
SimplePopper calls this function:
function PostPopover() {
return (
<div>
{PostData.map((list, index)=>{
return <div>
{list.email}
</div>
})}
</div>
)
}
Edit: added SimplePopper code
function PostPopover() {
return (
<div>
{PostData.map((list, index)=>{
return <div>
{list.email}
</div>
})}
</div>
)
}
const useStyles = makeStyles((theme) => ({
paper: {
border: '1px solid',
padding: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
},
}));
export default function SimplePopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
return (
<div>
<button aria-describedby={id} type="button" onClick={handleClick} color="primary">
Show email clicking here
</button>
<Popper id={id} open={open} anchorEl={anchorEl}>
<div className={classes.paper}><PostPopover />.</div>
</Popper>
</div>
);
}
Edit: PostData structure (1,000 fake records)
[{"id":1,
"first_name":"Geraldine",
"last_name":"Graal",
"email":"ggraal0#indiegogo.com"},
{"id":2,
"first_name":"Farris",
"last_name":"Sowten",
"email":"fsowten1#blogger.com"}]

Related

Add loader into the React page till data is loading

I have a onclick function to open a accounts information page
Below is My code
Index.tsx in this page i have clicked on the name and then all employees names are open in the another page
<p onClick={() => accountSelectorClick(rowData)}>{rowData.name}</p>
After click on the name the all data is sent to the EmployeesData page via props
<EmployeesData showPopup={showPopup} setPopup={setPopup} employee={employee}/>
account popup is used to show and hide the table from page
Below is the code to show employees data
const EmployeesData = (props) => {
let displayForm = props.showPopup ? "block" : "none";
const [loader, showLoader, hideLoader] = useFullPageLoader();
useEffect(() => {
showLoader();
},[]);
useEffect(() => {
if(props.employee.length !== 0) {
hideLoader();
}
},[props.employee]);
return (
<Container fluid="md" style={{ display: displayForm }}>
<div className="datatable-crud-demo datatable-filter-demo">
<Toast ref={toast} />
<div className="card" >
<div className='account-header'>
<h1>Accounts</h1>
</div>
{loader}
<DataTable ref={dt} value={props.employee} dataKey="id" paginator rows={15} filterDisplay="menu"
responsiveLayout="scroll" size="small">
<Column field="id" align='center' header="Id" sortable style={{ minWidth: '10rem', height:'1rem' }}></Column>
<Column field="name" align='center' header="Name" sortable style={{ minWidth: '10rem', height:'1rem' }}></Column>
</DataTable>
</div>
</div>
</Container>
)
};
EmployeesData.propTypes = {
showPopup: PropTypes.bool,
setPopup: PropTypes.func,
employee : PropTypes.any,
}
const mapStateToProps = (state: AppState) => {
return {}
}
const mapDispatchToProps = (dispatch: any) => {
return {};
};
export default connect(mapStateToProps, mapDispatchToProps)(memo(EmployeesData));
In this page i want to add a loader which can show the loader till all data is render,
I have tried with the useFullPageLoader hooks but it not working as expected,
Need a loader which can start the loading the data immideate after click on the Name button from employee table

How to use useState in loop?

I'm trying develop a little app in which on you can select multiple music album, using Next.js.
I display my albums like the image below, and I would like to add a check mark when clicked and hide it when clicked again.
My code looks like that :
import Image from "next/image";
import {Card,CardActionArea} from "#mui/material";
import { container, card } from "../styles/forms.module.css";
import album from "../public/album.json"
export default function Album() {
const albumList = {} ;
function addAlbum(albumId, image){
if ( !(albumId in albumList) ){
albumList[albumId] = true;
//display check on image
}
else{
delete albumList[albumId]
//hide check on image
}
console.log(albumList)
}
return (
<div className={container}>
{Object.keys(album.albums.items).map((image) => (
<Card className={card}>
<CardActionArea onClick={() => addAlbum(album.albums.items[image].id)}>
<Image alt={album.albums.items[image].artists[0].name} width="100%" height="100%" src={album.albums.items[image].images[1].url} />
</CardActionArea>
</Card>
))}
</div>
);
}
I know I should use useState to do so, but how can I use it for each one of my albums?
Sorry if it's a dumb question, I'm new with Hook stuff.
I think there are a few ways to go about this, but here is a way to explain the useState in a way that fits the question. CodeSandbox
For simplicity I made a Card component that knowns if it has been clicked or not and determines wither or not it should show the checkmark. Then if that component is clicked again a clickhandler from the parent is fired. This clickhandle moves the Card into a different state array to be handled.
The main Component:
export default function App() {
const [unselectedCards, setUnselectedCards] = useState([
"Car",
"Truck",
"Van",
"Scooter"
]);
const [selectedCards, setSelectedCards] = useState([]);
const addCard = (title) => {
const temp = unselectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setUnselectedCards(temp);
setSelectedCards([...selectedCards, title]);
};
const removeCard = (title) => {
console.log("title", title);
const temp = selectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setSelectedCards(temp);
setUnselectedCards([...unselectedCards, title]);
};
return (
<div className="App">
<h1>Current Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{unselectedCards.map((title) => (
<Card title={title} onClickHandler={addCard} key={title} />
))}
</div>
<h1>Selected Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{selectedCards.map((title) => (
<Card title={title} onClickHandler={removeCard} key={title} />
))}
</div>
</div>
);
}
The Card Component
export const Card = ({ onClickHandler, title }) => {
const [checked, setChecked] = useState(false);
const handleClickEvent = (onClickHandler, title, checked) => {
if (checked) {
onClickHandler(title);
} else {
setChecked(true);
}
};
return (
<div
style={{
width: "200px",
height: "250px",
background: "blue",
position: "relative"
}}
onClick={() => handleClickEvent(onClickHandler, title, checked)}
>
{checked ? (
<div
id="checkmark"
style={{ position: "absolute", left: "5px", top: "5px" }}
></div>
) : null}
<h3>{title}</h3>
</div>
);
};
I tried to make the useState actions as simple as possible with just a string array to help you see how it is used and then you can apply it to your own system.
You do not need to have a state for each album, you just need to set albumList as a state:
const [albumList, setAlbumList] = setState({});
function addAlbum(albumId, image) {
const newList = {...albumList};
if(!(albumId in albumList)) {
newList[albumId] = true;
} else {
delete albumList[albumId]
}
setAlbumList(newList);
}
And then in your loop you can make a condition to display the check mark or not by checking if the id is in albumList.

Update Child state from Parent using Context in React

I have a few buttons and "view all" button. The individual buttons load the coresponding data of that index or will show all the data by clicking the "view all" button. Problem I am running into is when I click my "view all" button in the parent it's not updating the state in the child component. On mounting it works as normal but on event handler in the "view all" it doesn't update. Any thoughts on where I am going wrong here?
JS:
...
const Context = createContext(false);
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ id, styles, discription }) => {
const { activeViewAll, handleChange } = useContext(Context);
const [toggleThisButton, setToggleThisButton] = useState();
const handleClick = () => {
setToggleThisButton((prev) => !prev);
handleChange(discription, !toggleThisButton);
};
return (
<>
<Avatar
className={toggleThisButton && !activeViewAll ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(toggleThisButton)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(false);
useEffect(() => {
setActiveViewAll(true);
setSelected([...data]);
}, []);
const handleChange = (val, action) => {
let newVal = [];
if (activeViewAll) {
selected.splice(0, 3);
setActiveViewAll(false);
}
if (action) {
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log("action", action);
setSelected(newVal);
};
const handleViewAll = () => {
console.log("all clicked");
setActiveViewAll(true);
setSelected([...data]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</Context.Provider>
);
}
Codesanbox:
https://codesandbox.io/s/72166087-forked-jvn59i?file=/src/App.js:260-3117
Issue
The issue seems to be that you are mixing up the management of the boolean activeViewAll state with the selected state.
Solution
When activeViewAll is true, pass the data array as the selected prop value to the ToggleContainer component, otherwise pass what is actually selected, the selected state.
Simplify the handlers. The handleViewAll callback only toggles the view all state to true, and the handleChange callback toggles the view all state back to false and selects/deselects the data item.
function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]); // none selected b/c view all true
const [activeViewAll, setActiveViewAll] = useState(true); // initially view all
const handleChange = (val, action) => {
setActiveViewAll(false); // deselect view all
setSelected(selected => {
if (action) {
return [...selected, val];
} else {
return selected.filter(v => v !== val)
}
});
};
const handleViewAll = () => {
setActiveViewAll(true); // select view all
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected} // pass all data, or selected only
/>
</div>
</Context.Provider>
);
}
In the ToggleContainer don't use the array index as the React key since you are mutating the array. Use the element value since they are unique and changing the order/index doesn't affect the value.
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update
Since it is now understood that you want to not remember what was previously selected before toggling activeViewAll then when toggling true clear the selected state array. Instead of duplicating the selected state in the children components, pass the selected array in the context and computed a derived isSelected state. This maintains a single source of truth for what is selected and removes the need to "synchronize" state between components.
const ToggleItem = ({ id, styles, description }) => {
const { handleChange, selected } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = () => {
handleChange(description);
};
return (
<>
<Avatar
className={isSelected ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(isSelected)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update the handleChange component to take only the selected value and determine if it needs to add/remove the value.
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(true);
const handleChange = (val) => {
setActiveViewAll(false);
setSelected((selected) => {
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [...selected, val];
}
});
};
const handleViewAll = () => {
setActiveViewAll(true);
setSelected([]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange, selected }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={d}>
<ToggleItem id={id} styles={classes} description={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected}
/>
</div>
</Context.Provider>
);
}

Parsing in props value from parent to component and display

Still new to ReactJS.
I have 3 JS pages: Home, Create, Edit.
From Home, you can navigate to the Create and Edit pages.
You can access the Create page anytime, but you need to call an API to populate some data before you can access the Edit page.
All 3 pages are using the same component, FormEntry. As its name, it generates basically a form input. Within this component, there are 2 functions: Search and AddEdit. Home is using the former, Create and Edit are using the later.
The flow is as such where when you click on the Create button, this will direct you to the Create page. The Create page will then display the form.
However, if you click on the Search button, this will call an API and generate data in a table. Each table row is clickable and clicking on them will direct you to the Edit page. For reusability, I parse in some values using props that, in theory, should populate the form fields based on which row I clicked on.
The issue I'm having is that though the value gets parsed in, the form field is not displaying the correct data. To be specific, the data from the responseData I parsed into the component is not displaying. And even if it does display, it's returning as 'undefined'.
What am I doing wrong?
Home.js
function HomePage() {
const [responseData, setData] = useState([]);
const navigateData = useNavigate();
function navigateToEdit(event){
navigateData({insert URL here}+event.id);
}
function getSearchData2(allData){
( allData.propRefNum !== "" ||
allData.client !== "" ||
allData.appSys !== "" ||
allData.status !== "" ? AxiosCall.searchProposal(JSON.stringify(allData)): AxiosCall.getProposals()
)
.then(
(result) => {
setData(result.data);
}
);
}
return (
<>
<div style={{ height: 400, width: '100%' }}>
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ flexGrow: 1 }}>
<DataGrid onRowClick={navigateToEdit} rows={dataRowObjs} columns={dataColObjs} headerAlign="center" disableColumnFilter />
</div>
</div>
</div>
</>
);
}
export default HomePage;
Edit.js
function EditPage() {
const [responseData, setData] = useState([]);
const { id } = useParams();
useEffect(() => {
const apiData = {
id: id
}
AxiosCall.getProposal(JSON.stringify(apiData))
.then(
(result) => {
setData(result.data);
}
);
},[]);
function getEditData(allData){
fetch({insert URL here}).then(
(result) => {
setData(result);
});
}
return <FormEntry.AddEditFormEntry title="Edit Proposal" defaultDataValue={responseData} responseInputData={getEditData} />
}
export default EditPage;
FormEntry component; AddEditForm
function AddEditFormEntry(props){
const propRefNumRef = useRef();
const descRef = useRef();
const clientRef = useRef();
const appSysRef = useRef();
const statusRef = useRef();
const remarkRef = useRef();
const vendorRef = useRef();
const { register, formState: { errors }, handleSubmit } = useForm();
function submitData(data){
//event.preventDefault();
const propRefNum = propRefNumRef.current.value;
const desc = descRef.current.value;
const client = clientRef.current.value;
const appSys = appSysRef.current.value;
const status = statusRef.current.value;
const remark = remarkRef.current.value;
const vendor = vendorRef.current.value;
const allData = {
propRefNum: propRefNum,
desc: desc,
client: client,
appSys: appSys,
status: status,
remark: remark,
vendor: vendor,
}
props.responseInputData(allData);
}
let defaultRefNum = props.defaultDataValue?.refNum; - **Note A: this is the line in question. When I console.log this variable, it displays the data correctly**
return(
<>
<form className="formEntry" onSubmit={handleSubmit(submitData)}>
<div style={{ display: 'flex'}} >
<div align="left" >
<RouterLink to={insert URL here} >
<IconButton aria-label="search" color="primary" >
<SkipPreviousIcon />
</IconButton>
</RouterLink >
</div>
<h1 style={{ flexGrow: 1, marginTop: -4 }} >{props.title}</h1>
<div align="right">
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
</div>
</div>
<br/>
<Stack justifyContent="center" direction="row" spacing={2} >
<Stack justifyContent="center" direction="column" spacing={2} >
<FieldEntry.TextEntry required="true" label="Proposal Reference Number" type="text" id="input_propRefNum" name="propRefNum" propsRef={propRefNumRef} value={defaultRefNum} />
**Referring to Note A above, I want to populate this field above. I am getting 'undefined', if not blank. If I am using the Create function, blank/undefined is expected. The Edit function is supposed to populate something here **
<FieldEntry.TextEntry label="Description" type="text" id="input_desc" name="desc" propsRef={descRef} value={props.defaultDataValue?.description} />
<FieldEntry.TextEntry required="true" label="Client" type="text" id="input_client" name="client" propsRef={clientRef} />
<FieldEntry.TextEntry required="true" label="Application System" type="text" id="input_appSys" name="appSys" propsRef={appSysRef} value={props.defaultDataValue?.appSystem} />
</Stack>
</Stack>
<br/>
</form>
</>
)
}
Note: I've removed a number of codes that does not pertain to the matter, to keep the sample code small. Rest assured that aside from my issue, everything is working as expected
I think a simpler example(which I just found out will have the same issue) would be
const [defaultRefNum, setRefNum] = useState("");
const [counter, setCounter] = useState(0);
let testValue = props.defaultDataValue?.refNum
useEffect(() => {
console.log("testValue2:",testValue)
if (props.defaultDataValue != null){
console.log("is not Null")
setCounter(c => c+1);
}
else{
console.log("is Null")
}
},[testValue]);
console.log("counter:",counter)
and
<FieldEntry.TextEntry value={counter} />
console.log output - counter: 2
Value in TextEntry: 0
I would assume the value in TextEntry should've outputted to be 2.

Pass id in component

I canĀ“t figure it out how to pass id as a prop in SimplePopper component.
I have this table:
<TableBody>
{PostData.map((list, index) => (
<TableRow key={index}>
<TableCell>{list.first_name}</TableCell>
<TableCell>{list.last_name}</TableCell>
<TableCell><SimplePopper key="{list.id}" /></TableCell>
</TableRow>
))}
</TableBody>
I want to pass id to SimplePopper:
function PostPopover() {
return (
<div>
{PostData.map((list, index)=>{
return <div key="id"> {list.email} </div>
})}
</div>
)
}
export default function SimplePopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
return (
<div>
<button aria-describedby={id} type="button" onClick={handleClick} color="primary">
Show email
</button>
<Popper id={id} open={open} anchorEl={anchorEl}>
<div className={classes.paper}><PostPopover />.</div>
</Popper>
</div>
);
}
This is PostData data structure:
[{"id":1,
"first_name":"Geraldine",
"last_name":"Graal",
"email":"ggraal0#indiegogo.com"},
{"id":2,
"first_name":"Farris",
"last_name":"Sowten",
"email":"fsowten1#blogger.com"}]
I know the mistake is in PostPopover because I got te full email list but no just the email related to the id, but can't figure it out why. Any idea?
You can just pass the id to ShowPopper as :
<ShowPopper id={list.id} />
Now you can access it in ShowPopper in props as:
const ShowPopper =(props) => {
console.log(props.id);
return <div>{props.id}</div>
}

Resources