Why I can not get ref as expected - reactjs

I have React App which detects hovered elements to perform the action of getting images. I would like to use ref to assure catching hover only from a specific areas of the page.
Working simplified version is like this:
const Form = props => {
const { fetchHints, hints, clearHints, fetchImages } = props;
const refSelect = React.useRef();
function getImages(e) {
if (e.key === "Enter") {
const withHover = getHovered(refSelect.current);
if (withHover) {
const className = withHover.className;
if (className.includes("option")) {
fetchImages(withHover.textContent);
history.push("./images");
}
}
}
}
return (
<div onKeyDown={debouncedGetImages}>
<div ref={refSelect}>
<SelectSection hints={hints} getValues={getValues} changeHandler={fetchImages} />
</div>
</div>
);
};
However, it is obvious that <div ref={refSelect}> only role is to be a point where the ref is anchored and it is useless besides. So, I tried to anchor ref just on SelectionSection:
<SelectSection hints={hints} getValues={getValues} changeHandler={fetchImages} ref={refSelect} />
while SelectionSection is as follows (any type is for initial simplicity):
const SelectSection = React.forwardRef((props: Props & RouteComponentProps, ref: any) => {
const { hints, getValues, changeHandler } = props;
const history = useHistory();
const path = props.match.path;
return hints?.length && path === "/" ? (
<Select
ref={ref}
className="select-top"
id="BigSelect"
value={getValues}
isClearable={true}
menuIsOpen={true}
onChange={selectValue => {
changeHandler(selectValue.value);
history.push("./images");
}}
options={hints}
/>
) : null;
});
export default withRouter(SelectSection);
The problem is that in this case refSelect is always undefined. What is wrong with this code?

Related

React: Passing input validation message from child component to parent form

Forgive me I've been through a lot of the existing questions around on this topic without success.
I have a filter dialog with several input fields (child components) and a submit button. I want the submit button to be disabled if a validation message exists on one of the child components.
Child (simplified):
const TextSearch: FC<TextSearchProps> = memo((props) => {
const [validationMessage, setValidationMessage] = useState('')
const onTextChange = (e: React.FormEvent<HTMLInputElement>) => {
const input = e.currentTarget.value
if(input && !checkNumericOnly(input)) {
setValidationMessage('Only numbers allowed')
} else {
setValidationMessage('')
}
return (
<div className={classes.textSearch}>
<InputText
className={validationMessage ? 'p-invalid' : ''}
onChange={onTextChange}
/>
<div className={classes.invalid}>{validationMessage}</div>
</div>
)
Parent:
const AdvancedSearch: FC<SearchProps> = memo(_props => {
const dispatch: AppDispatch = useDispatch()
const data = useSelector(selectSearchData())
const [validInput, setValidInput] = useState(true) // this is what I'm trying to set
How do I setValidInput based on whether the child validationMessage is true?
Have tried a function in the parent dialog:
function handleValidInput(valid: boolean) {
setValidInput(valid)
}
But have not been able to figure out how to call it from the child.
You should pass your setState function to your children and then update the state there:
In parent (AdvancedSearch):
const [validInput, setValidInput] = useState(true);
return (
<TextSearch setValidInput={setValidInput} />
)
In child (TextSearch):
const onTextChange = (e: React.FormEvent<HTMLInputElement>) => {
const input = e.currentTarget.value
if(input && !checkNumericOnly(input)) {
setValidationMessage('Only numbers allowed');
props.setValidInput(false);
} else {
setValidationMessage('');
props.setValidInput(true);
}
}
return (
<div className={classes.textSearch}>
<InputText
className={validationMessage ? 'p-invalid' : ''}
onChange={onTextChange}
/>
<div className={classes.invalid}>{validationMessage}</div>
</div>
)

How to use the code above in React js using hooks

document.getElementById("cards").onmousemove = e => {
for(const card of document.getElementsByClassName("card")) {
const rect = card.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
card.style.setProperty("--mouse-x", `${x}px`);
card.style.setProperty("--mouse-y", `${y}px`);
};
}
I actually don't know how to use the above code in react js. so, if anyone knows please respond!
full source code link:
https://codepen.io/Hyperplexed/pen/MWQeYLW
to use Hook you need to handle with reference of element like this
const CardRef = React.useRef(null);
useShadow(CardRef);
return <div ref={CardRef} className="card" ></div>
And the hook would be something like this
import { useEffect } from 'react';
const useShadow = (reference: React.MutableRefObject<any>) => {
useEffect(() => {
const eventReference = (e) => {
const rect = reference.current.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
reference.current.style.setProperty('--mouse-x', `${x}px`);
reference.current.style.setProperty('--mouse-y', `${y}px`);
};
if (reference.current) {
const { current } = reference;
current.addEventListener('mousemove', eventReference);
}
return () => {
reference.current &&
reference.current.removeEventListener('mousemove', eventReference);
};
}, [reference]);
};
export default useShadow;
First of all, React does provide SyntheticEvents, so your onmousemove would probably look like this in React:
<div onMouseMove={ yourEventHandler } />
I can see what you are trying to do is to set the children .card's properties when the mouse had moved. What you can do is to have useState() in the parent .cards container to store the latest mouse position, then pass that state as props into the children. Something like:
export function Cards() {
const [mouseX, setMouseX] = useState(0);
const [mouseY, setMouseY] = useState(0);
const myOnMouseMove = (e)=> {
// set your state using setMouseX(), setMouseY()
}
return (
<div className='cards' onMouseMove={myOnMouseMove}>
<Card className='card' mouseX={mouseX} mouseY={mouseY} />
<Card className='card' mouseX={mouseX} mouseY={mouseY} />
...
</div>
)
}
(Not real implementation, just the concept)

React Component is rerendering, even though it does not depend on the state

In my React code I have to use a legacy component, which makes a setup api call when it is first rendered. The component has a custom completion/cancelation event which I use to trigger a State update. The current Code looks like this:
export const useOneTimePassword = (
headline = "OTP anfordern",
id = "opt",
type = "sms",
businessProcess = "otp-process"
): UseOneTimePasswordReturn => {
const [otpCode, setOtpCode] = useState<undefined | string>();
const [isOtpCancelled, setIsOtpCancelled] = useState<boolean>(false);
const openOtp = () => {
const otp = document.querySelector(`otp-component#${id}`) as OtpElement;
otp.open();
};
const OtpComponent: FC = () => (
<Otp
headline={headline}
id={id}
type={type}
businessProcess={businessProcess}
setIsOtpCancelled={setIsOtpCancelled}
setOtpCode={setOtpCode}
/>
);
return {
otpCode,
isOtpCancelled,
openOtp,
OtpComponent,
removeOtp: () => {
setOtpCode(undefined);
},
};
};
and for the Component it looks like this:
const Otp: React.FC<OtpProps> = ({
headline,
businessProcess,
type,
id,
setOtpCode,
setIsOtpCancelled,
}) => {
function onOtpResponse(e: CompletedEvent) {
if (e.detail.otpCode) {
setOtpCode(e.detail.otpCode);
setIsOtpCancelled(false);
} else {
setIsOtpCancelled(true);
}
}
const ref = useRef();
useEffect(() => {
//#ts-ignore
if (ref.current) ref.current.addEventListener("completed", onOtpResponse);
}, []);
return (
<otp-component
ref={ref}
headline={headline}
id={id}
type={type}
business-process={businessProcess}
/>
);
};
export default Otp;
What I do not understand is that state changes in otpCode aswell as isOtpCancelled cause a rerender of the OtpComponent

persist state after page refresh in React using local storage

What I would like to happen is when displayBtn() is clicked for the items in localStorage to display.
In useEffect() there is localStorage.setItem("localValue", JSON.stringify(myLeads)) MyLeads is an array which holds leads const const [myLeads, setMyLeads] = useState([]); myLeads state is changed when the saveBtn() is clicked setMyLeads((prev) => [...prev, leadValue.inputVal]);
In DevTools > Applications, localStorage is being updated but when the page is refreshed localStorage is empty []. How do you make localStorage persist state after refresh? I came across this article and have applied the logic but it hasn't solved the issue. I know it is something I have done incorrectly.
import List from './components/List'
import { SaveBtn } from './components/Buttons';
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: "",
});
const [display, setDisplay] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const localStoredValue = JSON.parse(localStorage.getItem("localValue")) ;
const [localItems] = useState(localStoredValue || []);
useEffect(() => {
localStorage.setItem("localValue", JSON.stringify(myLeads));
}, [myLeads]);
const saveBtn = () => {
setMyLeads((prev) => [...prev, leadValue.inputVal]);
// setLocalItems((prevItems) => [...prevItems, leadValue.inputVal]);
setDisplay(false);
};
const displayBtn = () => {
setDisplay(true);
};
const displayLocalItems = localItems.map((item) => {
return <List key={item} val={item} />;
});
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
);
}
export default App;```
You've fallen into a classic React Hooks trap - because using useState() is so easy, you're actually overusing it.
If localStorage is your storage mechanism, then you don't need useState() for that AT ALL. You'll end up having a fight at some point between your two sources about what is "the right state".
All you need for your use-case is something to hold the text that feeds your controlled input component (I've called it leadText), and something to hold your display boolean:
const [leadText, setLeadText] = useState('')
const [display, setDisplay] = useState(false)
const localStoredValues = JSON.parse(window.localStorage.getItem('localValue') || '[]')
const handleChange = (event) => {
const { name, value } = event.target
setLeadText(value)
}
const saveBtn = () => {
const updatedArray = [...localStoredValues, leadText]
localStorage.setItem('localValue', JSON.stringify(updatedArray))
setDisplay(false)
}
const displayBtn = () => {
setDisplay(true)
}
const displayLocalItems = localStoredValues.map((item) => {
return <li key={item}>{item}</li>
})
return (
<main>
<input name="inputVal" value={leadText} type="text" onChange={handleChange} required />
<button onClick={saveBtn}> Save </button>
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
)

Using React hook form getValues() within useEffect return function, returns {}

I'm using react-hook-form library with a multi-step-form
I tried getValues() in useEffect to update a state while changing tab ( without submit ) and it returned {}
useEffect(() => {
return () => {
const values = getValues();
setCount(values.count);
};
}, []);
It worked in next js dev, but returns {} in production
codesandbox Link : https://codesandbox.io/s/quirky-colden-tc5ft?file=/src/App.js
Details:
The form requirement is to switch between tabs and change different parameters
and finally display results in a results tab. user can toggle between any tab and check back result tab anytime.
Implementation Example :
I used context provider and custom hook to wrap setting data state.
const SomeContext = createContext();
const useSome = () => {
return useContext(SomeContext);
};
const SomeProvider = ({ children }) => {
const [count, setCount] = useState(0);
const values = {
setCount,
count
};
return <SomeContext.Provider value={values}>{children}</SomeContext.Provider>;
};
Wrote form component like this ( each tab is a form ) and wrote the logic to update state upon componentWillUnmount.
as i found it working in next dev, i deployed it
const FormComponent = () => {
const { count, setCount } = useSome();
const { register, getValues } = useForm({
defaultValues: { count }
});
useEffect(() => {
return () => {
const values = getValues(); // returns {} in production
setCount(values.count);
};
}, []);
return (
<form>
<input type="number" name={count} ref={register} />
</form>
);
};
const DisplayComponent = () => {
const { count } = useSome();
return <div>{count}</div>;
};
Finally a tab switching component & tab switch logic within ( simplified below )
const App = () => {
const [edit, setEdit] = useState(true);
return (
<SomeProvider>
<div
onClick={() => {
setEdit(!edit);
}}
>
Click to {edit ? "Display" : "Edit"}
</div>
{edit ? <FormComponent /> : <DisplayComponent />}
</SomeProvider>
);
}

Resources