I have a search input and showing the result like popover on bottom.
https://prnt.sc/sg11m9
const AutoComplete = connectAutoComplete(
({ hits, currentRefinement, refine }) => {
const [focus, isFocus] = useState(false);
return(
<div>
<input
type="search"
placeholder="Search movies"
value={currentRefinement}
onChange={event => refine(event.currentTarget.value)}
onFocus={() => isFocus(true)}
onBlur={() => isFocus(false)}
/>
{
focus && currentRefinement ?
( <><ul onMouseOver={() => isFocus(true)} >
{hits.map(hit => (
<li key={hit.objectID}>
<Link to={`/movies/${hit.slug}`}><Highlight tagName="mark" hit={hit} attribute="title" /></Link>
</li>
))}
</ul>
<CustomStateResults /></>
) : ''
}
</div>
)
}
);
I set a state for onFocus and onBlur attribute,so that the popover shows when if its in focus and hide when its onBlur.
but my problem is, I have a link on popover, If I click the link it triggers the onBlur, so technically the popover hides and doesnt go to link.
How can I click on the links inside wihtout triggering the onblur and I also need to hide the popover when you click anywhere the DOM or if youre not onfocus on textbox?
I just solved it!
the onBlur function is not good.
It should be, when you click outside the component that will trigger the setstate value of focus into false.
heres the code
function useOnClickOutside(ref, handler) {
useEffect(
() => {
const listener = event => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
},
[ref, handler]
);
}
const AutoComplete = connectAutoComplete(
({ hits, currentRefinement, refine }) => {
const [focus, isFocus] = useState(false);
const ref = useRef();
useOnClickOutside(ref, () => isFocus(false));
return(
<div>
<input
type="search"
placeholder="Search movies"
value={currentRefinement}
onChange={event => refine(event.currentTarget.value)}
onFocus={() => isFocus(true)}
/>
{
focus && currentRefinement ?
( <><ul ref={ref}>
{hits.map(hit => (
<li key={hit.objectID}>
<Link to={`/movies/${hit.slug}`}><Highlight tagName="mark" hit={hit} attribute="title" /></Link>
</li>
))}
</ul>
<CustomStateResults /></>
) : ''
}
</div>
)
}
);
Suggestions and tips are still welcome. Thanks!
Related
import React, { useState, useEffect } from "react";
import "./style.css";
function App() {
const [movieData, setMovieData] = useState();
const [directorData, setDirectorData] = useState();
const [listData, setListData] = useState([]);
const submitHandler = () => {
setListData([
...listData,
{
movie: movieData,
director: directorData,
},
]);
setMovieData("");
setDirectorData("");
};
const removeHandler = (id) => {
const newlist = listData.filter((remId) => {
return remId !== id;
});
setListData(newlist)
};
return (
<div className="App">
<div>
<h1>Todo App</h1>
</div>
<div className="form">
<div>
<label>Movie</label>
<input
type="text"
placeholder="type items"
value={movieData}
onChange={(e) => setMovieData(e.target.value)}
/>
</div>
<div>
<label>Director</label>
<input
type="text"
placeholder="type items"
value={directorData}
onChange={(e) => setDirectorData(e.target.value)}
/>
</div>
<div>
<button onClick={submitHandler}>Submit</button>
</div>
</div>
<div>
{listData.map((item, index) => {
return (
<li key={index}>
<span>{item.movie}</span>, <span>{item.director}</span>{" "}
<span style={{ marginLeft: "2rem" }}>
<button onClick={removeHandler(index)} style={{ color: "red" }}>
X
</button>
</span>
</li>
);
})}
</div>
</div>
);
}
export default App;
As soon as I added removeHandler, I am getting too many renders onSubmit. It's working fine once I remove removeHandler.
Is there any other method to remove items from the list apart from filter and splice(just mention the function name)?
I have even tried using useEffect but the same problem persists
Change to onClick={() => removeHandler(index)}
and you also have to remove 'return' in your array.filter
remId !== id;
});
This happens because you're not passing an arrow function to onClick.
You're writing onClick={removeHandler(index)} instead of onClick={() => removeHandler(index)}.
It's because functionName(param) syntax is generally used to call functions so when you write that, it causes infinite renders.
use this function some new changes
const removeHandler = (id) => { const newlist = [...listData];
newlist.splice(id, 1); setListData(newlist); };
and change onclick event
onClick={() => removeHandler(index)}
I have 2 popup's(I reuse CloseButton(component) and Modal(component) in 2 popup's) and need to do focus trap at all. I lf answer 4 better way.
1 popup Screen, components: ModalLogin-Modal-CloseButton.
I read about some hooks: useRef() and forwardRef(props, ref)
but i don't undestand why it's not work in my case. I am trying to find a solution. I need help :)
In ModalLogin, I try to do a focus trap. To do this, I mark what should happen with focus when moving to 1 and the last element. I need to pass my ref hook obtained via Modal-CloseButton. I read that you can't just transfer refs to functional components. I try to use the forwardref hook in the necessary components where I transfer it, here's what I do:
All links without focus-trap and hook's!.
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/form-login/modal-login.jsx [Modal-login full]
const ModalLogin = () => {
const topTabTrap* = useRef();
const bottomTabTrap* = useRef();
const firstFocusableElement = useRef();
const lastFocusableElement = useRef();
useEffect(() => {
const trapFocus = (event) => {
if (event.target === topTabTrap.current) {
lastFocusableElement.current.focus()
}
if (event.target === bottomTabTrap.current) {
firstFocusableElement.current.focus()
}
}
document.addEventListener('focusin', trapFocus)
return () => document.removeEventListener('focusin', trapFocus)
}, [firstFocusableElement, lastFocusableElement])
return (
<Modal onCloseModal={() => onCloseForm()} ref={lastFocusableElement}>
<form >
<span ref={topTabTrap} tabIndex="0" />
<Logo />
<Input id="email" ref={firstFocusableElement} />
<Input id="password" />
<Button type="submit" />
<span ref={bottomTabTrap} tabIndex="0"/>
</form>
</Modal>
);
};
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/modal/modal.jsx [Modal full]
const Modal = forwardRef(({ props, ref }) => {
const { children, onCloseModal, ...props } = props;
const overlayRef = useRef();
useEffect(() => {
const preventWheelScroll = (evt) => evt.preventDefault();
document.addEventListener('keydown', onEscClick);
window.addEventListener('wheel', preventWheelScroll, { passive: false });
return () => {
document.removeEventListener('keydown', onEscClick);
window.removeEventListener('wheel', preventWheelScroll);
};
});
const onCloseModalButtonClick = () => {
onCloseModal();
};
return (
<div className="overlay" ref={overlayRef}
onClick={(evt) => onOverlayClick(evt)}>
<div className="modal">
<CloseButton
ref={ref}
onClick={() => onCloseModalButtonClick()}
{...props}
/>
{children}
</div>
</div>
);
});
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/close-button/close-button.jsx [CloseButton full]
const CloseButton = forwardRef(({ props, ref }) => {
const {className, onClick, ...props} = props;
return (
<button className={`${className} close-button`}
onClick={(evt) => onClick(evt)}
tabIndex="0"
ref={ref}
{...props}
>Close</button>
);
});
And now i have a lot of errors just like: 1 - Cannot read properties of undefined (reading 'children') - Modal, 2 - ... className undefined in CloseButton etc.
2 popup Screen, components: Modal(reuse in 1 popup) - InfoSuccess- CloseButton(reuse in 1 popup)
I have only 1 interactive element - button (tabindex) and no more. Now i don't have any idea about 2 popup with focus-trap ((
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/success-modal/success-modal.jsx [SuccessModal full]
const SuccessModal = ({ className, onChangeVisibleSuccess }) => {
return (
<Modal onCloseModal={() => onChangeVisibleSuccess(false)}>
<InfoSuccess className={className} />
</Modal>
);
};
https://github.com/j3n4r3v/ligabank-credit/blob/master/src/components/info-block/info-block.jsx [Infoblock full]
const InfoBlock = ({ className, title, desc, type }) => {
return (
<section className={`info-block ${className} info-block--${type}`}>
<h3 className="info-block__title">{title}</h3>
<p className="info-block__desc">{desc}</p>
</section>
);
};
const InfoSuccess = ({ className }) => (
<InfoBlock
title="Спасибо за обращение в наш банк."
desc="Наш менеджер скоро свяжется с вами по указанному номеру телефона."
type="center"
className={className}
/>
);
I know about 3 in 1 = 1 component and no problem in popup with Focus-Trap. But i want understand about my case, it's real to life or not and what best practice.
I am trying to replicate how when you hover over a particular movie tile on Netflix, after sometime the movie tile expands to display more information.
Currently I have succeeded on expanding the tile to display more information on hover but setting a timeout such that the information is only displayed after the mouse has hovered for over 1000ms is proving to produce unwanted results.
So far i have tried this but the problem is that when i hover the state is changed for all other movie tiles instead of only the one that was hovered
function RowPoster({ movie, isTrending }) {
const [isHovered, setisHovered] = useState(false);
const trailerUrl = movie.trailer_url;
return (
<div
className={`RowPoster ${isTrending && "isTrending"}`}
onMouseEnter={setTimeout(() => setisHovered(true), 1000)}
onMouseLeave={() => setisHovered(false)}
>
<img src={movie.cover_image} alt={movie.titles.en} />
{isHovered && (
<>
{
<ReactPlayer
className="video"
width="100%"
height="160px"
loop={true}
playing={false}
url={trailerUrl}
/>
}
<div className="item__info">
<h4>{movie.titles.en}</h4>
<div className="icons">
<PlayArrow className="icon" />
<Add />
<ThumbUpAltOutlined className="icon" />
<ThumbDownOutlined className="icon" />
<KeyboardArrowDown className="icon" />
</div>
<div className="stats">
<span className="stats_score">{`Score ${movie.score}%`}</span>
</div>
<div className="genre">
<ul className="genre_items">
<li>{movie.genres[0]}</li>
<li>{movie.genres[1]}</li>
<li>{movie.genres[2]}</li>
<li>{movie.genres[3]}</li>
</ul>
</div>
</div>
</>
)}
</div>
);
}
export default RowPoster;
In the code you provided, a timeout is called when rendering. For this reason, the state changes for each movie tiles that is rendered. To call setTimeout when an event is fired, you need to wrap it in a function:
...
onMouseEnter={() => setTimeout(() => setisHovered(true), 1000)}
...
For behavior "display information after the mouse has hovered for more than 1000 ms", you will need a little more code:
function RowPoster({ movie, isTrending }) {
const [isHovered, setisHovered] = useState(false);
const trailerUrl = movie.trailer_url;
const hoverTimerRef = useRef();
const handleCancelHover = useCallback(() => {
if (hoverTimerRef.current) {
clearTimeout(hoverTimerRef.current);
}
}, []);
const handleMouseEnter = useCallback(() => {
// save the timer id in the hoverTimerRef
hoverTimerRef.current = setTimeout(() => setisHovered(true), 1000);
}, []);
const handleMouseLeave = useCallback(() => {
// cancel the scheduled hover if the mouseLeave event is fired before the timer is triggered
handleCancelHover();
setisHovered(false);
}, [handleCancelHover]);
useEffect(() => {
return () => {
// cancel the scheduled hover when unmounting the component
handleCancelHover();
};
}, [handleCancelHover]);
return (
<div
className={`RowPoster ${isTrending && "isTrending"}`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
...
I'm trying to build an input component with a clear button using react#17
import { useRef } from 'react';
const InputWithClear = props => {
const inputRef = useRef();
return (
<div>
<input
ref={inputRef}
{...props}
/>
<button
onClick={() => {
inputRef.current.value = '';
inputRef.current.dispatchEvent(
new Event('change', { bubbles: true })
);
}}
>
clear
</button>
</div>
);
};
using this component like:
<InputWithClear value={value} onChange={(e) => {
console.log(e); // I want to get a synthetic event object here
}} />
but the clear button works once only when I did input anything first, and stop working again.
if I input something first and then click the clear button, it does not work.
why not using?
<button
onClick={() => {
props.onChange({
target: { value: '' }
})
}}
>
clear
</button>
because the synthetic event object will be lost
So, how do I manually trigger a synthetic change event of a react input component?
Try this approach,
Maintain state at the parent component level (Here parent component is App), onClear, bubble up the handler in the parent level, and update the state.
import React, { useState } from "react";
import "./styles.css";
const InputWithClear = (props) => {
return (
<div>
<input {...props} />
<button onClick={props.onClear}>clear</button>
</div>
);
};
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<InputWithClear
value={value}
onChange={(e) => {
console.log(e); // I want to get a synthetic event object here
setValue(e.target.value);
}}
onClear={() => {
setValue("");
}}
/>
</div>
);
}
Working code - https://codesandbox.io/s/youthful-euler-gx4v5?file=/src/App.js
you should use state to control input value rather than create useRef, that's the way to go. you can use a stopPropagation prop to control it:
const InputWithClear = ({value, setValue, stopPropagation = false}) => {
const onClick = (e) => {
if(stopPropagation) e.stopPropagation()
setValue('')
}
return (
<div>
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
<button
onClick={onClick}
>
clear
</button>
</div>
);
};
export default function App() {
const [value, setValue] = useState('')
return (
<div className="App">
<InputWithClear value={value} setValue={setValue} stopPropagation />
</div>
);
}
I'm constructing an input field in React that looks like so:
When the 'x' is clicked (StyledCloseCircle), the text will be cleared, and the 'x' symbol should disappear. The 'x' symbol is currently shown with javascript when the input field is focused,
export const Search = React.forwardRef((props, ref) => {
const [isFocused, setFocus] = useState(false);
const [isHovered, setHover] = useState(false);
return (
<InputContainer
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<StyledInput
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
isHovered={isHovered}
ref={ref}
{...props}
/>
{isFocused && !props.value && (
<StyledMagnifyingGlass
isHovered={isHovered}
isFocused={isFocused}
onClick={props.onSearch}
/>
)}
{isFocused && props.value && (
<StyledCloseCircle onClick={() => console.log("THIS DOES NOT FIRE")} />
)}
{!isFocused && (
<StyledMagnifyingGlass
isHovered={isHovered}
isFocused={isFocused}
onClick={props.onSearch}
/>
)}
</InputContainer>
);
});
The issue is that when the 'x' is clicked, the input looses focus, which causes the 'x' to be removed on the next render, and does not register the click event. It does, however, trigger the mousedown event.
Therefore, my two questions are:
What is the order of operations when the 'x' is clicked, that leads it to registering mousedown but not click?
How can I achieve the desired behavior?
You should create a separate state to control where to show/hide the Clear button. Show it onFocus even as you do now but hide it if user clicks outside of the input container or if clicks on the Clear button. You can additionally hide it onBlur but with some timeout (500-1000ms) in case if user uses keyboard instead of a mouse.
This is a CodeSnadbox example of the code below:
function App() {
const inputContainerRef = useRef();
const [value, setValue] = useState("");
const [showClear, setShowClear] = useState(false);
const onFocus = useCallback(() => {
setShowClear(true);
}, []);
const onClear = useCallback(() => {
setValue("");
setShowClear(false);
}, []);
const onOutsideClick = useCallback(e => {
// hide Clear button only if clicked outside of the container
if (!inputContainerRef.current.contains(e.target)) {
setShowClear(false);
}
}, []);
useLayoutEffect(
() => {
// set the listener only if we shown the Clear button and remove the listener once we hid it
if (showClear) {
document.addEventListener("click", onOutsideClick);
return () => document.removeEventListener("click", onOutsideClick);
}
},
[showClear] // re-invoke if the state changes
);
return (
<div className="App">
<div className="input-container" ref={inputContainerRef}>
<input
value={value}
onChange={e => {
setValue(e.target.value);
}}
className="input"
type="tetxt"
onFocus={onFocus}
/>
{showClear && (
<div className="clear" onClick={onClear}>
X
</div>
)}
</div>
</div>
);
}