NextJS: sidebar routing - currently clicked link - javascript

I recently asked this question, and found help to make my sidebar links respond by displaying a component in the main div of my page.
Now I want to figure out how to use this where I have more than one option in my sidebar. I added library and tasks, but when I click library - it reveals the library component in the main div (I want this), but when I then click tasks, it reveals both library and tasks components. I only want the tasks component to show in that event.
What is the js command to setState to false if the link is not the most recently clicked link?
function Sidebar({ toggleLibrary, toggleTasks }) {
// const { me, loading } = useMe()
return (
<Stack spacing="1" >
<NavButton label="Tasks" fontWeight="normal" onClick={toggleTasks} />
<NavButton label="Deals" fontWeight="normal" onClick={toggleLibrary}/>
</Stack>
)
}
const DashBase = () => {
const isDesktop = useBreakpointValue({ base: false, lg: true })
// the menu component should initially be hidden
const [showLibrary, setShowLibrary] = React.useState(false)
const [showTask, setShowTask] = React.useState(false)
// state setter to switch between our `true` and `false` states
const toggleLibrary = () => setShowLibrary(!showLibrary)
const toggleTasks = () => setShowTask(!showTask)
const router = useRouter()
return (
<Flex
>
{isDesktop ? <Sidebar toggleLibrary={toggleLibrary} toggleTasks={toggleTasks} /> : <Navbar />}
<Container py="8" flex="none">
{showLibrary ? <Library /> : '' }
{showTask ? <Tasks /> : '' }

Currently the code has an independent state for each item you want to toggle. While you could enqueue state updates to toggle all other states false when a new item is toggled true, this is rather unmaintainable code.
If the goal is to only ever render a single active item then I'd suggest just using a single active state and set the value in a single callback passed to the Sidebar component.
Example:
const DashBase = () => {
const isDesktop = useBreakpointValue({ base: false, lg: true });
// nothing initially is active
const [showActive, setShowActive] = React.useState(null);
// state setter to switch between our `null` and "active" id states
const toggleActive = (key) => setShowActive(
active => active === key ? null : key
);
const router = useRouter()
return (
<Flex>
{isDesktop ? <Sidebar toggleActive={toggleActive} /> : <Navbar />}
<Container py="8" flex="none">
{showActive === 'library' && <Library />}
{showActive === 'tasks' && <Tasks />}
...
...
function Sidebar({ toggleActive }) {
return (
<Stack spacing="1" >
<NavButton
label="Tasks"
fontWeight="normal"
onClick={() => toggleActive("tasks")}
/>
<NavButton
label="Deals"
fontWeight="normal"
onClick={() => toggleActive("library")}
/>
</Stack>
);
}

Related

How to Close Floating Menu when clicking anywhere else but the menu? React JS

I was struck while creating a Floating menu in react JS, the task required is to close the floating menu when a click is recorded anywhere outside the menu, the code snippet is as follows
class SectionMenu extends Component {
state = {
isOpen: false,
activeSection: '',
sectionIds: [],
coordinates: []
}
render() {
const { sections, hideMenu } = this.props;
const { sectionIds } = this.state;
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={this.state.isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
iconResting={}
iconActive={}
onClick={() => this.setState({isOpen:
!this.state.isOpen})}
size={}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
}
I am struck on how to create a useRef/useEffect hook function, is there any method to to it done without useRef as well?
if not how do I do it with useRef/useEffect and/or any other hook?
Try to refactor your code using functional component:
const SectionMenu = ({ sections, hideMenu }) => {
const [sectionsIds, setSectionsIds] = useState([]);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
// whatever effect
}, [])
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
onClick={() => setIsOpen((prev) => !prev)}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
Yes, I've ran up into the same issue and this worked for me . You can change the onClick() as follows
let isClicked=true;
const {/*function name here*/} =()=>{
if(isClicked){
document.querySelector("{/*classname here*/}").style = "display:block "
isClicked=false;
}
else{
document.querySelector("{/*classname here*/}").style = "display:none"
isClicked=true;
}
}
This will toggle between true and false.

I need to open and close accordion based on arrow click

I am using Material UI accordion my issue is if I click on the arrow accordion will get open but again I click on the arrow it will not get closed I need to set it when the user clicks on the arrow according will close and open based on the arrow click check code sandbox link for better understanding.
export default function ControlledAccordions() {
const [expanded, setExpanded] = React.useState(false);
// const handleChange = (panel) => (event, isExpanded) => {
// setExpanded(isExpanded ? panel : false);
// };
const handleChange = (pannel) => {
setExpanded(pannel);
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return (
<Accordion expanded={expanded === `panel${i}`}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
handleChange(`panel${i}`);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
fdsfdsf
</AccordionSummary>
<AccordionDetails>dfdf</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
Code SandBox Link
you need to reset panel in that case. You can do that in change handler.
const handleChange = (pannel) => {
setExpanded(expended === pannel ? '' : pannel);
};
when you click the already expanded panel, it just sets it to be expanded again.
you need to check whether the clicked panel is already expanded and if so collapse it instead of expanding it:
const handleChange = (pannel) => {
if (expanded === pannel) setExpanded(false);
else setExpanded(pannel);
};
Create another component called MyAccordian and keep toggling accordion logic in that component. That way you don't need to handle toggling for each and every component separately.
export default function ControlledAccordions() {
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return <MyAccordian value={value} />;
})}
</div>
);
}
const MyAccordian = ({ value }) => {
const [expanded, setExpanded] = React.useState(false);
return (
<Accordion expanded={expanded}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
setExpanded((prev) => !prev);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails>{value}</AccordionDetails>
</Accordion>
);
};
Working Demo
export default function ControlledAccordions() {
// initial state, everything is closed,
const [expandedIndex, setExpandedIndex] = React.useState(-1);
// this should be handleClic
const handleChange = (index) => {
// in useState, current expandedIndex is passed as the argument
// whatever we return will be set as the expandedIndex
setExpandedIndex((currentIndex) => {
// if any box is open, currentIndex will be that index
// when I click on the open box, it will set the expandedIndex=-1
if (currentIndex === index) {
return -1;
} else {
// If I reached here, that means I am on a closed box
// when I click I swithc the expandedIndex to current box's index
return index;
}
});
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
// when handleChange runs on AccordionSummary expandedIndex===i
// that means when i click on the current box, it will be open
const isExpanded = expandedIndex === i;
return (
<Accordion expanded={isExpanded}>
<AccordionSummary
onClick={() => handleChange(i)}
expandIcon={
// I dont know #mui/material too much.
// main question is "I need to open and close accordion based on arrow click"
<ExpandMoreIcon
onClick={() => handleChange(i)}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails
style={{ backgroundColor: "green" }}
>{`box index ${i} is open`}</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
proof of work:
const handleChange = (pannel) => {
setExpanded(!pannel);
};

React setting the state of all rendered items with .map in parent component

I have a parent component with a handler function:
const folderRef = useRef();
const handleCollapseAllFolders = () => {
folderRef.current.handleCloseAllFolders();
};
In the parent, I'm rendering multiple items (folders):
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
name={folder.name}
content={folder.content}
id={folder.id}
ref={folderRef}
/>
))}
In the child component I'm using the useImperativeHandle hook to be able to access the child function in the parent:
const [isFolderOpen, setIsFolderOpen] = useState(false);
// Collapse all
useImperativeHandle(ref, () => ({
handleCloseAllFolders: () => setIsFolderOpen(false),
}));
The problem is, when clicking the button in the parent, it only collapses the last opened folder and not all of them.
Clicking this:
<IconButton
onClick={handleCollapseAllFolders}
>
<UnfoldLessIcon />
</IconButton>
Only collapses the last opened folder.
When clicking the button, I want to set the state of ALL opened folders to false not just the last opened one.
Any way to solve this problem?
You could create a "multi-ref" - ref object that stores an array of every rendered Folder component. Then, just iterate over every element and call the closing function.
export default function App() {
const ref = useRef([]);
const content = data.map(({ id }, idx) => (
<Folder key={id} ref={(el) => (ref.current[idx] = el)} />
));
return (
<div className="App">
<button
onClick={() => {
ref.current.forEach((el) => el.handleClose());
}}
>
Close all
</button>
{content}
</div>
);
}
Codesandbox: https://codesandbox.io/s/magical-cray-9ylred?file=/src/App.js
For each map you generate new object, they do not seem to share state. Try using context
You are only updating the state in one child component. You need to lift up the state.
Additionally, using the useImperativeHandle hook is a bit unnecessary here. Instead, you can simply pass a handler function to the child component.
In the parent:
const [isAllOpen, setAllOpen] = useState(false);
return (
// ...
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
isOpen={isAllOpen}
toggleAll={setAllOpen(!isAllOpen)}
// ...
/>
))}
)
In the child component:
const Child = ({ isOpen, toggleAll }) => {
const [isFolderOpen, setIsFolderOpen] = useState(false);
useEffect(() => {
setIsFolderOpen(isOpen);
}, [isOpen]);
return (
// ...
<IconButton
onClick={toggleAll}
>
<UnfoldLessIcon />
</IconButton>
)
}

Reload user after deleting an item from it with React

Using React.Js When trying to delete users from the end of the list the deleted users are replaced by the last item in the list. Only on page refresh do the deleted users actually go away and the user list updates.The back-end is work because I can delete the user but I need to refresh to see a new update
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
const combinedStyles = combineStyles(popupStyles, UserPageStyles);
export default function RemoveUser(props) {
const global = useContext(GlobalContext);
const [open, setOpen] = React.useState(false);
let org = {}
if (global.state.selectedOrg && Object.keys(global.state.selectedOrg).length !== 0) {
org = global.state.selectedOrg
} else if (global.state.defaultOrg && Object.keys(global.state.defaultOrg).length !== 0) {
org = global.state.defaultOrg
}
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
props.handleClose();
};
const handleSubmit = async () => {
let result = await global.api.removeAccount({
id: props.account._id,
role: global.state.user && global.state.user.document.roles[0] || '',
accountRole: props.type === 'patients' ? 'patient' : props.type === 'providers' ? 'doctor' : props.type === 'moas' ? 'oa' : props.type === 'admins' ? 'healthOrgAdmin' : props.type,
org: org._id,
username: props.account.username,
});
if (result && result.status === 200) {
handleClose();
props.refresh();
} else {
alert('Unable to remove account.');
}
}
const classes = combinedStyles();
return (
<div>
<ButtonBase className={props.className} onClick={handleClickOpen}> <Typography className={classes.typography}>Remove</Typography></ButtonBase>
<Dialog
open={open}
TransitionComponent={Transition}
keepMounted
onClose={handleClose}
aria-labelledby="alert-dialog-slide-title"
aria-describedby="alert-dialog-slide-description"
>
<DialogTitle className={classes.dialogTitle} id="alert-dialog-slide-title">Remove Account<IconButton onClick={handleClose} className={classes.dialogClose} children={<ClearIcon />} /> </DialogTitle>
<DialogContent>
<DialogContentText className={classes.contentText}>
Are you sure you want to remove {props.account.contact_name}'s account? You will not be able to revert this.
</DialogContentText>
</DialogContent>
<DialogActions className={classes.dialogAction}>
<Button onClick={handleClose} color="primary" className={classes.actionBtn}>
Cancel
</Button>
<Button onClick={handleSubmit} color="primary" className={classes.actionBtn}>
Yes
</Button>
</DialogActions>
</Dialog>
</div>
);
}
Usually after the delete request completes (with a 200) you would have to set some new state to trigger a re-render. Do you have a list of all the users in state somewhere? you need to do something like setUsers(users.filter(u => u.id !== deletedUserId))
There are many tricks to re-render your react components :
Re-render component when state changes
Any time a React component state has changed, React has to run the render() method. So you maybe you should affect your users list to a state so that when the list of users change (the state) it re-render your component.
Re-render component when props change
Re-render with key prop
Force a re-render by this.forceUpdate();
Take a look on that Methods to force a re-render in React

How to remove text from previous element if condition is not satisfied - reactjs

I'm making small app in react js which basically would display SELECTED if item is selected.
Here is my code:
import React, { useState } from 'react';
function SelectedFiles(props) {
const [selectedFile, setSelectedFile] = useState(0);
const selectSelectedFileOnChange = id => {
setSelectedFile(id);
props.onSetSelectedFile(id);
};
return (
<MainContainer>
<RadioButton
key={props.id}
value={props.id}
name="Acfile"
onChange={e => {
selectSelectedFileOnChange(props.id);
}}
disabled={false}
></RadioButton>
<span>{props.file.name}</span>
<span>{props.file.size}</span>
<span>{props.file.isPrimary === true ? 'SELECTED' : null}</span>
</MainContainer>
);
}
export default SelectedFiles;
This component is part of parent component and purpose of this component is just to display an items:
<AddF className="modal-body">
{docs && docs.length > 0
? docs.map(file => (
<SelectedFiles
key={file.id}
id={file.id}
file={file}
onSetSelectedFile={handleSetPrimaryFile}
/>
))
: null}
</AddF>
const handleSetPrimaryFile = id => {
props.onSetPrimaryFile(id);
};
As its possible to see guys I dont know how to remove text from NOT SELECTED element..
Thanks guys ! Cheers
Could you please try below and see if it works?
const [files, setFiles] = useState({docs[0].id: true});
<AddF className="modal-body">
{docs && docs.length > 0
? docs.map(file => (
<SelectedFiles
key={file.id}
id={file.id}
file={file}
isPrimary={!!files[file.id]}
onSetSelectedFile={handleSetPrimaryFile}
/>
))
: null}
</AddF>
const handleSetPrimaryFile = id => {
setFiles({[id]: true});
props.onSetPrimaryFile(id);
};
Change SelectedFiles.js as
<span>{props.isPrimary === true ? 'SELECTED' : null}</span>
Hope it helps.
This snippet {props.file.isPrimary === true ? 'SELECTED' : null} is determining when SELECTED should appear. But I don't see where props.file would ever change.
I also see you using both the useState hook and some sort of prop function passed in to handle selection.
The solution is to have some sort of unique identifier for the files (perhaps that's file.id), then check this value on the selectedFile to determine if SELECTED should appear, e.g., props.file.id === selectedFile.

Categories