Reputation: 18859
I am confused about something related to using functional components with React.
Here is my component, the implementation details are not all that important:
const Trending = ({ match, actions, trendingPlaces, meConfig }) => {
const [GeoView, setGeoView] = useState(AcceptCurrentLocationPlaceholder);
const menuItem = match.params[0];
console.log('*** Trending ***');
if (runsOnClient()) {
useEffect(() => {
console.log('effect is run');
if (!requiresGeolocation(menuItem)) return;
if (meConfig.isCustomLocationSet) {
setGeoView(
getComponentForMenuItem(menuItem));
return;
}
if (geolocationServiceIsAvailable()) {
const getCurrentLocation = async () => {
try {
setGeoView(AcceptCurrentLocationPlaceholder);
const currentPosition = await getCurrentPosition(
getConfig().googleMaps.currentPositionOptions);
setGeoView(getComponentForMenuItem(menuItem));
} catch (err) {
setGeoView(DeniedCurrentLocationPlaceholder);
}
};
getCurrentLocation();
} else {
// Display the view mentioning 'geolocation is not available' and set custom location
}
}, [menuItem]);
}
// Fetch trending places
const { fetchTrendingPlaces } = actions;
useEffect(() => {
fetchTrendingPlaces();
}, []);
return (
<section id="zp-trending">
<TrendingMenu match={ match } />
<div id="zp-trending-body">
<Route path={'/trending/list'} component={ GeoView } />
<Route path={'/trending/map'} component={ GeoView } />
<Route path={'/trending/settings'} component={ TrendingSettings } />
</div>
</section>
);
};
const mapStateToProps = state => {
return {
meConfig: state.meConfig,
trendingPlaces: state.trendingPlaces
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(fetchTrendingPlaces, dispatch)
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Trending);
When I check my logs, it appears that *** Trending ***
gets called a bit inconsistently. It would appear that it gets called after every Redux update as well.
Why is that?
Upvotes: 0
Views: 960
Reputation: 18859
The problem was that I'm using useState
here which triggers a re-render.
The other re-renders were triggered from:
Actually within useEffect it seems that I have to use useState to get the conditional rendering. So I guess that the behaviour is as expected.
The UI behaves as expected so I guess that the re-renders are necessary.
Upvotes: 0
Reputation: 45121
The problem is that bindActionCreators
always returns a new object or function. So you get new actions
on every redux update.
If you want to keep the nested structure you could do the following
const mapDispatchToProps = (dispatch) => {
const actions = bindActionCreators(fetchTrendingPlaces, dispatch);
return () => ({actions})
};
Or you could flatten the properties so it would pass shallow equality check.
const mapDispatchToProps = {
fetchTrendingPlaces
}; // pass object to connect
// use in component
const Trending = ({fetchTrendingPlaces /* the rest of your props*/}) => {
Upvotes: 2
Reputation: 6846
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page). By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.
here is the part of your code that breaks the rules
if (runsOnClient()) {
useEffect(() => {
console.log('effect is run');
if (!requiresGeolocation(menuItem)) return;
if (meConfig.isCustomLocationSet) {
setGeoView(
getComponentForMenuItem(menuItem));
return;
}
Fix may help.
Upvotes: 1