I’m trying to learn React by solving a basic JavaScript problem in React. I googled but still no luck, not sure how to solve it. Here is a repro on stackblitz.
import { FC } from 'react';
import './style.css';
export const App: FC<{ name: string }> = ({ name }) => {
const emailInput = document.querySelector('#email');
const emailRegex = '//';
emailInput.addEventListener('keyup', (e) => {
console.log((e.target as HTMLButtonElement).value);
if (emailRegex.test((e.target as HTMLButtonElement).value)) {
emailInput.parentElement.classList.add('valid');
} else {
emailInput.parentElement.classList.remove('valid');
}
});
return (
<div>
<form>
<div className="field valid">
<label for="email">Enter your email:</label>
<input type="text" name="email" id="email" />
<span className="material-icons tick">done</span>
</div>
</form>
</div>
);
};
I’m getting this error:
Error in /~/src/App.tsx (9:16)
Cannot read properties of null (reading 'addEventListener')
2
Answers
You should avoid relying on the DOM with React. Instead, use states to store the value of the input and apply the class dynamically:
Demo
The problem is that the component tree hasn’t rendered by the time your above code is running. You could fix this by wrapping your event listener code in a
useEffect
:this would make the code wait to execute until after react has rendered the DOM. This is unidiomatic however. React really encourages you to write code in a declarative style. Try to avoid manipulating the DOM and instead express UI as a function of state if at all possible. We would begin by adding the event listener in the jsx:
this removes the imperative event listener manipulation but we still have the classes being manipulated. You could go about fixing this by creating some state to hold whether the input is valid or not:
doing things this way is alright, but by overwhelming convention we almost always store the current value of the
input
as a state and then derive information based on that. This will help minimize your state footprint and help to make invalid states unrepresentable as your application grows in complexity. We can also inline our event handler if desired since it’s getting pretty small.the last thing is that by convention in react we use the
onChange
event as a sensible default.we now have idiomatic react.
Hopefully this long winded explanation helps you understand how to think about going from imperative DOM manipulation to declarative idiomatic react