I'm facing a problem with the functional component. I'm using a react-router-dom package for handling the routes. What I have is a list that contains links and buttons and when we click on the button a dropdown is opened. This dropdown also contains links.
- Whenever user will change the route all dropdowns will close.
- Whenever new dropdown is opened all others will also close.
How I'm manging the state of these dropdowns?
I'm treating the state of every dropdown as a separate object and onClick listener will pass a key / property as an argument that has a boolean value. Based on that value a class will be added on the dropdown.
I already achieve this with the help of a class component.
Class component:
import React, { Component } from "react";
import { Link, withRouter } from "react-router-dom";
class ToggleClass extends Component {
state = {};
toggle = dropdownName => {
// If it has the true value then turn into false, It is a behavior of a toggle.
if (this.state[dropdownName]) {
this.setState({ [dropdownName]: false });
}
// If there is not any dropdown is opened it means state object is empty
// Object.keys will return an empty array then please open that dropdown.
else if (Object.keys(this.state).length === 0) {
this.setState({ [dropdownName]: true });
}
// Else block will ony trigger when the user clicked on other dropdown,
// then close the last dropdown and just open the new one
else {
Object.keys(this.state).forEach(i => {
this.setState({ [i]: false });
});
this.setState({ [dropdownName]: true });
}
};
onRouteChanged = () => {
// Take a state object and convert all its keys into an array
// and set it to false whenever a route changes.
Object.keys(this.state).forEach(i => {
// pageMenu: false
this.setState({ [i]: false });
});
};
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.location !== prevProps.location) {
this.onRouteChanged();
}
}
componentDidMount() {
this.onRouteChanged();
}
render() {
return (
<ul className="list">
<li>
<Link to="/link1">Link 1</Link>
</li>
<li>
<button onClick={() => this.toggle("dropdown1")}>Dropdown 1</button>
<ul className={this.state["dropdown1"] ? "open" : "hide"}>
<li>
<Link to="/link2">Link 2</Link>
</li>
<li>
<Link to="/link3">Link 3</Link>
</li>
</ul>
</li>
<li>
<button onClick={() => this.toggle("dropdown2")}>Dropdown 2</button>
<ul className={this.state["dropdown2"] ? "open" : "hide"}>
<li>
<Link to="/link4">Link 4</Link>
</li>
<li>
<Link to="/link5">Link 5</Link>
</li>
</ul>
</li>
</ul>
);
}
}
export default withRouter(ToggleClass);
But the problem is on the functional component by using hooks. react-router-dom has a hook named useLocation which returns the current pathname. But onRouteChanged function is updating the state. I cannot use this function inside useEffect hook it turns into an infinite loop.
useEffect(() => {
onRouteChanged();
}, [location, onRouteChanged]);
But If I remove the onRouteChanged from the dependency array then linter will warn about this
React Hook useEffect has a missing dependency: 'onRouteChanged'. Either include it or remove the dependency array react-hooks/exhaustive-deps
useEffect(() => {
onRouteChanged();
}, [location]);
Functional Component:
import React, { useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";
const ToggleFunc = () => {
const location = useLocation();
const [state, setState] = useState({});
const onRouteChanged = () => {
Object.keys(state).forEach(i => {
setState({ [i]: false });
});
};
const toggle = dropdownName => {
if (state[dropdownName]) {
setState({ [dropdownName]: false });
} else if (Object.keys(state).length === 0) {
setState({ [dropdownName]: true });
} else {
Object.keys(state).forEach(i => {
setState({ [i]: false });
});
setState({ [dropdownName]: true });
}
};
useEffect(() => {
onRouteChanged();
}, [location]);
return (
<ul className="list">
<li>
<Link to="/link1">Link 1</Link>
</li>
<li>
<button onClick={() => toggle("dropdown1")}>Box 1</button>
<ul className={state["dropdown1"] ? "open" : "hide"}>
<li>
<Link to="/link2">Link 2</Link>
</li>
<li>
<Link to="/link3">Link 3</Link>
</li>
</ul>
</li>
<li>
<button onClick={() => toggle("dropdown2")}>Box 2</button>
<ul className={state["dropdown2"] ? "open" : "hide"}>
<li>
<Link to="/link4">Link 4</Link>
</li>
<li>
<Link to="/link5">Link 5</Link>
</li>
</ul>
</li>
</ul>
);
};
export default ToggleFunc;
Can you guide me What is the optimal way to fix these errors?