I have a react Sidebar component that appear when i toggle a state. I added a fadeInLeft animation class with Animate.style so i thought it would be nice to have a fadeOutLeft effect too whenever i set the state to false condition. All went smooth for the fadeInLeft class effect but when i toggle the state to be false the component just disappear without the fadeOutEffect class that i added, how is that happened?
video of it: https://www.loom.com/share/ff26dc014bbb43649419b1ecd6bae01e
Sidebar Component
const Sidebar = ({ isDisplayed }) => {
if(isDisplayed === true) {
return(
<Box
position="absolute"
top={0}
left={0}
width="155px"
height="100vh"
bgColor="#1e1e1e"
className={`${Styles.SidebarBox} ${isDisplayed ?'animate__animated animate__fadeInLeft' : 'animate__animated animate__fadeOutLeft'}`}
>
<Text color="#fff">Test</Text>
</Box>
)
}
}
Main Component
const Navbar = () => {
const [display, setDisplay] = useState(false)
const newRef = useRef(null)
const handleClickOutside = (e) => {
if(newRef.current && !newRef.current.contains(e.target)){
setDisplay(false)
}
}
useEffect(() => {
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
}
},[])
return (
<Flex
justifyContent="space-between"
alignItems="center"
w="100%"
h="110px"
px="150px"
py="15px"
bgColor="#1e1e1e"
boxShadow="md"
ref={newRef}
>
<Sidebar isDisplayed={display} />
How can i have that fadeOutLeft effect whenever i change my display state to false?
Related
please solve it if you can do, I made a function when I scroll down, I can show the "button" {this is basically an arrow which indicates from bottom to top}. I want to add another function that when I scroll down >500 it will show the button, and if I scroll up it will hide, and if I stop scrolling if my window is scrolled >500 it will show otherwise it will hide.
export default function ScrollToTop() {
const [isVisible, setIsVisible] = useState(false);
const ScrollToTop= () => {
window.scrollTo({
top: 0,
behavior: "smooth"
});
};
useEffect(() => {
// Button is displayed after scrolling for 500 pixels
const toggleVisibility = () => {
if (window.pageYOffset > 500) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};
window.addEventListener("scroll", toggleVisibility);
return () => window.removeEventListener("scroll", toggleVisibility);
}, []);
return (
<div className="scroll-to-top">
{isVisible && (
<div className="top" onClick={scrollToTop}>
<div className="top_img_holder">
<Image src="/uparrow.png" width="16" height="12" alt="" />
</div>
</div>
)}
</div>
)
}
To add the behavior you described, you can try the following:
Add a useRef hook to store a reference to the previous scroll
position.
Add an event listener for the scroll event in the component's
useEffect hook, and update the component's state (using the
setIsVisible function) based on the current and previous scroll
positions.
Return the component's state (isVisible) from the useEffect hook's
callback function, so that the effect is re-run whenever isVisible
changes.
import { useState, useEffect, useRef } from "react";
export default function ScrollToTop() {
const [isVisible, setIsVisible] = useState(false);
const prevScrollPos = useRef(0);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: "smooth"
});
};
useEffect(() => {
const toggleVisibility = () => {
const currentScrollPos = window.pageYOffset;
// Button is displayed after scrolling for 500 pixels
if (currentScrollPos > 500 && currentScrollPos > prevScrollPos.current) {
setIsVisible(true);
} else {
setIsVisible(false);
}
prevScrollPos.current = currentScrollPos;
};
window.addEventListener("scroll", toggleVisibility);
return () => window.removeEventListener("scroll", toggleVisibility);
}, [isVisible]);
return (
<div className="scroll-to-top">
{isVisible && (
<div className="top" onClick={scrollToTop}>
<div className="top_img_holder">
<Image src="/uparrow.png" width="16" height="12" alt="" />
</div>
</div>
)}
</div>
);
}
I want to use an MUI stepper to replace a Select component. The select component is used to indicate the status of the document the user is working in (New, In Progress, Complete, etc.). I have managed to display the correct status in the stepper, but I cannot interact with it to move the status forward or back.
This is my stepper file. I am passing the status value through props:
export default function IntakeStatusBar(props) {
const { status } = props;
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const steps = ["New", "In Progress", "Completed"];
useEffect(() => {
if (status === "In Progress") {
setActiveStep(1);
} else if (status === "Completed") {
setActiveStep(2);
} else setActiveStep(0);
}, [status, activeStep]);
const handleStep = (step) => () => {
setActiveStep(step);
};
return (
<div className={classes.root}>
<Stepper activeStep={activeStep} alternativeLabel>
{steps.map((label, index) => (
<Step key={label}>
<StepButton onClick={handleStep(index)}>{label}</StepButton>
</Step>
))}
</Stepper>
</div>
);
}
This is where I call and display the stepper:
export default function IntakeDetails() {
const [details, setDetails] = useState("");
const onTextChange = (e) => {
var id = e.target.id ? e?.target.id : e?.target.name;
var value = e.target.value;
setDetails({ ...details, [id]: value });
}
....
return (
<IntakeStatusBar status={details?.Status} onChange={onTextChange} />
// This is the Select drop down menu I have been using
<TextField
label="Status"
error={requiredField && details?.Status?.length <= 0}
value={details?.Status}
disabled={!(adminRole && isSolutionsTab && details?.Status !== "In Plan")}
select
onChange={onTextChange}
>
{details.StatusList?.map((choice) => {
return (
<MenuItem key={choice} value={choice}>
{choice}
</MenuItem>
);
})}
</TextField>
)
}
This is what the status field looks like in JSON:
{
Status: "New"
}
besides changing this:
<StepButton onClick={() => handleStep(index)}>{label}</StepButton>
you have to change this:
const handleStep = (step) => {
setActiveStep(step);
};
and set Stepper to nonLinear if you want user to click on steps:
<Stepper nonLinear activeStep={activeStep} alternativeLabel>
I also commented out useEffect since I had no idea what its purpose is and it's messing with activeStep state.
I'm trying to wrap a modal in a different component but only the first showed up.
So I searched for a way to code my modal so I can accept different component open and close the right component instead of open the first component even if i click in another button
{open && <Modal>
<TeamForm />
</Modal>}
{openSavecard && <Modal>
<HikingSaveCards items={cartItems}/>
</Modal>}
// only the first one will display
const Modal = ({children}: Props) => {
const ContentRef = useRef<HTMLDivElement>(null)
const [open,setOpen] = useState(true)
useEffect(() => {
if(!open) return ;
function listener(e:any) {
if (ContentRef.current?.contains(e.target)) return;
setOpen(prev => !prev)
}
window.addEventListener('click', listener)
return () =>{ window.removeEventListener('click', listener)};
}, [open])
return open ? createPortal(
<>
<Background >
<Content ref={ContentRef}>
{children}
</Content>
</Background>
</>, document.querySelector('#portal')!) : null
};
export default Modal
I have an outsideAlerter component that functions elsewhere on my site. I am now using it on a repeatable component and for some reason it is clearing my state effectively breaking my desired outcome.
below is my wrapper component that detects if you click outside of its children
import React, { useRef, useEffect } from "react";
/**
* Hook that alerts clicks outside of the passed ref
*/
function useOutsideAlerter(ref, onClickOutside) {
useEffect(() => {
/**
* Alert if clicked on outside of element
*/
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
//console.log(onClickOutside);
onClickOutside();
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
}
/**
* Component that alerts if you click outside of it
*/
export default function OutsideAlerter(props) {
const wrapperRef = useRef(null);
useOutsideAlerter(wrapperRef, props.onClickOutside);
return <div ref={wrapperRef}>{props.children}</div>;
}
Below is my controller component, it handles state
const TableFilterDropdownController = ({style, rows, colKey, activeFilters, addActiveFilter}) => {
const [tableFilterState, setTableFilterState] = useState(
{
state: INACTIVE,
iconColor: "black",
filter: "",
filteredRows: [...rows],
localActiveFilters: []
}
);
useEffect(() => {
let state = tableFilterState.state;
let localActiveFilters = tableFilterState.localActiveFilters;
if (state === INACTIVE && localActiveFilters.length > 0) {
setTableFilterState({...tableFilterState, state: ACTIVE})
}
}, [tableFilterState.state])
//filter out repeats and rows that don't match input
useEffect(() => {
let filter = tableFilterState.filter
if (filter !== "") {
let tempFilteredRows = [];
rows.map(row => {
if (row[colKey].toLowerCase().includes(filter.toLowerCase()) &&
!tempFilteredRows.includes(row[colKey])) {
tempFilteredRows.push(row[colKey]);
}
})
setTableFilterState({...tableFilterState, filteredRows: tempFilteredRows})
}
else {
let tempFilteredRows = [];
rows.map(row => {
if (!tempFilteredRows.includes(row[colKey])) {
tempFilteredRows.push(row[colKey]);
}
})
setTableFilterState({...tableFilterState, filteredRows: tempFilteredRows});
}
}, [tableFilterState.filter, rows])
const onClick = () => {
if (tableFilterState.state === DROP_DOWN) {
console.log(tableFilterState)
if (tableFilterState.localActiveFilters.length > 0) {
//setState(ACTIVE)
setTableFilterState({...tableFilterState, state: ACTIVE});
}
else {
//setState(INACTIVE)
setTableFilterState({...tableFilterState, state: INACTIVE});
}
}
else {
//setState(DROP_DOWN)
setTableFilterState({...tableFilterState, state: DROP_DOWN});
}
}
//something here is breaking it and resetting on click outside
const onClickOutside = () => {
setTableFilterState({...tableFilterState, state: INACTIVE});
}
let addLocalActiveFilter = (filter) => {
let newActiveFilters = [...tableFilterState.localActiveFilters];
const index = newActiveFilters.indexOf(filter);
if (index > -1) {
newActiveFilters.splice(index, 1);
} else {
newActiveFilters.push(filter);
}
setTableFilterState({...tableFilterState, localActiveFilters: newActiveFilters});
}
return (
<TableFilterDropdown
style={style}
color={tableFilterState.iconColor}
state={tableFilterState.state}
onClick={onClick}
onClickOutside={onClickOutside}
dropLeft={true}
filter={tableFilterState.filter}
setFilter={e => setTableFilterState({...tableFilterState, filter: e.target.value})}
>
{tableFilterState.filteredRows.map((item, index) => {
return (
<CheckboxInput
value={item}
label={item}
key={index}
onChange={e => {
addActiveFilter(e.target.value);
addLocalActiveFilter(e.target.value)
}}
isChecked={tableFilterState.localActiveFilters.includes(item)}
/>
);
})}
</TableFilterDropdown>
);
}
export default TableFilterDropdownController;
And lastly below is the UI component
const TableFilterDropdown = ({style, state, color, children, onClick, onClickOutside, dropLeft, filter, setFilter}) => {
useEffect(() => {
console.log("state change")
console.log(state);
}, [state])
return (
<div
className={`sm:relative inline-block ${style}`}
>
<OutsideAlerter onClickOutside={onClickOutside}>
<IconButton
type="button"
style={`relative text-2xl`}
onClick={onClick}
>
<IconContext.Provider value={{color: color}}>
<div>
{state === DROP_DOWN ?
<AiFillCloseCircle /> :
state === ACTIVE ?
<AiFillFilter /> :
<AiOutlineFilter />
}
</div>
</IconContext.Provider>
</IconButton>
{state === DROP_DOWN ?
<div className={`flex flex-col left-0 w-screen sm:w-32 max-h-40 overflow-auto ${dropLeft ? "sm:origin-top-left sm:left-[-2.5rem]" : "sm:origin-top-right sm:right-0"} absolute mt-2 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10`} role="menu" aria-orientation="vertical" aria-labelledby="menu-button">
<SearchBar label={"Search"} placeholder={"Search"} value={filter} onChange={setFilter} />
{children}
</div>
: null}
</OutsideAlerter>
</div>
);
For some reason whenever you click outside the component the tableFilterState gets set to
{
state: INACTIVE,
iconColor: "black",
filter: "",
filteredRows: [],
localActiveFilters: []
}
Which is not intentional, the tableFilterState should stay the same, only state should change. I can't figure this out so please help!!!
When you call useOutsideAlerter and pass onClickOutside handler it captures tableFilterState value and use it in a subsequent calls. This is a stale state. You could try this approach or use refs as described in docs:
const onClickOutside = () => {
setTableFilterState(tableFilterState => ({
...tableFilterState,
state: INACTIVE,
}));
}
EDIT: it should be show / hide 'Hi' on click of 'Open' so basically clicking 'Open' is equivalent to clicking 'Hello', I'm sorry.
I have a <details> element and a <p> element, what I want to do is on click of the <Menu /> component, toggle the <Details /> component on and off (show / hide 'Hi' on click of 'Hello'). Here is the code I've tried:
import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
const Menu = ({ toggleDetails }) => {
return (
<div>
<p onClick={toggleDetails}>Open</p>
</div>
);
};
const Details = (isOpen) => {
const detailsRef = useRef();
// detailsRef.current.open = isOpen;
return (
<details ref={detailsRef}>
<summary>Hello</summary>
<div>Hi</div>
</details>
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleDetails = () => {
setIsOpen(isOpen ? false : true);
};
return (
<div>
<Details isOpen={isOpen} />
<Menu toggleDetails={toggleDetails} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
A code sandbox: https://codesandbox.io/s/react-playground-forked-25ott?file=/index.js
I feel I'm close, on change of isOpen state, the Details component is re-rendered and I want to toggle detailsRef.current.open to true or false, but detailsRef.current is undefined so the commented line does not work, how can I achieve this?
If you mean what I think you mean when you say "toggle details on and off", you're looking for a conditional render:
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleDetails = () => {
setIsOpen(isOpen ? false : true);
};
return (
<div>
{isOpen && <Details />}
<Menu toggleDetails={toggleDetails} />
</div>
);
};
You don't need a ref at all, nor does Details need the isOpen prop.
To toggle the visibility of hi on click of hello, you would do the same:
const Details = (isOpen) => {
const [showHi, setShowHi] = useState(true)
return (
<details>
<summary onClick={() => setShowHi(!showHi)}>Hello</summary>
{showHi && <div>Hi</div>}
</details>
);
};