Trace
Trace

Reputation: 18859

React functional component re-renders with Redux

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?

enter image description here

Upvotes: 0

Views: 960

Answers (3)

Trace
Trace

Reputation: 18859

The problem was that I'm using useState here which triggers a re-render.

The other re-renders were triggered from:

  • The useEffect that requests the trending items, but this is normal as I want them on initial render.
  • The match property that is passed for react-router. This is also intentional as the component is re-rendered when the url changes.

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

Yury Tarabanko
Yury Tarabanko

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

Edgar
Edgar

Reputation: 6846

Read the react hooks rules.

Only Call Hooks at the Top Level

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.)

Only Call Hooks from React Functions

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

Related Questions