Reputation: 1298
I am using React to create a search feature. I have used this way of doing it before but not with React and it has worked. Now it comes up with an error in the console saying "Uncaught TypeError: Cannot read property 'addEventListener' of null". Could this have anything to do with it being made in react? How do I fix this?
<body>
<div id="searching"></div>
<script type="text/babel">
class SearchBox extends React.Component { render(){ return(
<form>
<input type="text" className="searchbox" />
</form>) } } ReactDOM.render(
<SearchBox />, document.getElementById("searching"));
</script>
JS
const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';
const repositories = [];
fetch(endpoint)
.then(blob => blob.json())
.then(data => repositories.push(...data));
function findMatches(wordToMatch, repositories) {
return repositories.filter(place => {
const regex = new RegExp(wordToMatch, "gi");
return place.city.match(regex) || place.state.match(regex);
});
};
function displayMatches() {
const matchArray = findMatches(this.value, repositories);
const html = matchArray.map(place => {
const regex = new RegExp(this.value, "gi");
const cityName = place.city.replace(regex, `<span class="hl">${this.value}</span>`);
const stateName = place.state.replace(regex, `<span class="hl">${this.value}</span>`);
return `
<li>
<span class="name">${cityName}, ${stateName}</span>
<span class="population">${numberWithCommas(place.population)}</span>
</li>
`;
}).join('');
console.log(matchArray);
suggestions.innerHTML = html;
};
const searchInput = document.querySelector(".searchbox");
const suggestions = document.querySelector(".suggestions");
searchInput.addEventListener("change", displayMatches);
searchInput.addEventListener("keyup", displayMatches);
Upvotes: 4
Views: 15417
Reputation: 45
I agree with @Sean. Native java and ReactJS has different life cycle to handle events. I'm adding some who is using hooks. You can add custom hook using useEffect and useRef.
Here is a sample code to create useEventListener custom hooks. I've got this sample code from https://usehooks.com/useEventListener/
function useEventListener(eventName, handler, element = window){
// Create a ref that stores handler
const savedHandler = useRef();
// Update ref.current value if handler changes.
// This allows our effect below to always get latest handler ...
// ... without us needing to pass it in effect deps array ...
// ... and potentially cause effect to re-run every render.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(
() => {
// Make sure element supports addEventListener
// On
const isSupported = element && element.addEventListener;
if (!isSupported) return;
// Create event listener that calls handler function stored in ref
const eventListener = event => savedHandler.current(event);
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
},
[eventName, element] // Re-run if eventName or element changes
);
};
export default useEventListener;
Upvotes: 2
Reputation: 306
Depending on the above answers and reference from other places. I tried the below and worked out.
class Searchbox extends React.Component {
state = {};
componentDidMount = () => { //Used for when component mounts for first time
myfunction();
}
componentDidUpdate = () => { //Used for every time component updates
myfunction();
}
myfunction = () => {
// This is function which contains addEventListeners
// or statements like - document.getElementId("myDiv");
}
render() {
return (
<div id="myDiv">
...
// body of code to render
</div>
);
}
}
export default Searchbox;
Upvotes: 0
Reputation: 564
I can't agree with Sean. It's fine to use native functions, and indeed you sometimes have to.
The reason you get this error is that your script is trying to add a listener to a non-existent DOM node: when your listener script executes, your input element has not yet been flushed to the DOM. This is the reason that when you need to reach for an escape hatch in this way, you should do it from within the component itself, in compnentDidMount, and only when a child needs to listen on a completely unconnected parent DOM node - one which for instance, may be outside other React subtree yiur component belongs to. This guarantees the node you're trying to find already exists.
That being said, using native functions this way a an escape hatch that should and can be avoided in 95% of common use cases. You should use more idiomatic ways, either by hoisting state to a higher level component, or using a ref if you really need to.
Upvotes: 4
Reputation: 6125
As a general rule, it's a bad idea to mix ReactJS with non-ReactJS ways of doing things. React has a very specific way of dealing with events, and it doesn't play nice with direct calls to addEventListener()
. There's a small chance that when you call addEventListener()
it'll work sometimes, but you really shouldn't rely on that.
The reason you shouldn't mix the two is that React is free to rebuild the DOM any time it wants to: Any time there is a state change, React is allowed to tear down the DOM elements and make new ones, at its discretion, not yours. You neither know nor care when the elements are rendered, and that's part of the beauty of React.
So then how do you deal with events? You have to wire them up in what seems like an old-school kind of way using attributes like onChange
and onKeyUp
, at the time the elements are created. (Under the hood, React will actually use addEventListener
at the right times, but you never really notice that happening.)
A revised version of your code, then, might be structured something like this:
class SearchBox extends React.Component {
constructor(props) {
super(props);
// This binding is necessary to make `this` work in the callback
this.onKeyUp = this.onKeyUp.bind(this);
this.onChange = this.onChange.bind(this);
}
onChange(event) {
...
}
onKeyUp(event) {
...
}
render() {
return(
<form>
<input type="text" className="searchbox" onChange={this.onChange} onKeyUp={this.onKeyUp} />
</form>
);
}
}
ReactDOM.render(
<SearchBox />,
document.getElementById("searching")
);
As you can see, the whole structure of the code is different. React isn't just a way to easily emit HTML elements: It's an entire component-based micro-framework around which you design your entire UI.
Upvotes: 8