John Valdetine
John Valdetine

Reputation: 446

How to prevent re-render with reselect?

I'm trying to reselect middleware today and prevent unnecessary re-rendering.

Here is my reducer.js:

const INITIAL_STATE = {
  dogs: 100,
  cats: 12312302384
};

const pets = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case "CHANGE_DOGS":
      return {
        ...state, dogs: state.dogs + 1
      };
    case "CHANGE_CATS":
      return {
        ...state, cats: state.cats + 1
      };
    default:
      return { ...state };
  }
};

export default pets;

Here is my main.js:

import React from "react";
import { createSelector } from "reselect";
import { useSelector, useDispatch } from "react-redux";

function ReduxDeneme(props) {
  // Selectors - Classic style - NON Memoized!
//   const dogsData = useSelector(state => state.pets.dogs);
//   const catsData = useSelector(state => state.pets.cats);

  //                          Dogs rendering..  --> First opening..
  // 10:11:28.070 index.js:18 CATS rendering.. --> First opening..
  // 10:11:29.703 index.js:13 Dogs rendering.. --> Press "ChangeDogs" button.
  // 10:11:29.703 index.js:18 CATS rendering..  --> Press "ChangeDogs" button.
  // 10:11:33.143 index.js:13 Dogs rendering.. --> Press "ChangeCats" button.
  // 10:11:33.143 index.js:18 CATS rendering..  --> Press "ChangeCats" button.

  // Selectors - Memoized version RESELECT middleware'i ile..
  const dogsDataMemo = createSelector(
    state => state.pets.dogs,
    dogs => dogs
  );
  const catsDataMemo = createSelector(
    state => state.pets.cats,
    cats => cats
  );

  const dogsData = useSelector(dogsDataMemo)
  const catsData = useSelector(catsDataMemo)


  // Components
  const Dogs = ({ dogsData }) => {
    console.log("Dogs rendering..");
    return <h1>{dogsData}</h1>;
  };

  const Cats = ({ catsData }) => {
    console.log("Cats rendering..");
    return <h1>{catsData}</h1>;
  };

  // Actions

  const dispatch = useDispatch();
  const changeDogs = () => dispatch({ type: "CHANGE_DOGS" });
  const changeCats = () => dispatch({ type: "CHANGE_CATS" });

  return (
    <div>
      <Dogs dogsData={dogsData} />
      <Cats catsData={catsData} />
      <button onClick={changeDogs}>Change Dogs</button>
      <button onClick={changeCats}>Change CATS</button>
    </div>
  );
}

export default ReduxDeneme;

Could someone explain the correct way or where am I doing wrong?

Upvotes: 1

Views: 1680

Answers (2)

John Valdetine
John Valdetine

Reputation: 446

First of all, I thank @tudor very much for his efforts. All of his said is correct.

But, I want to show that Reselect works.

SCENARIO 1 - NON-MEMOIZED

import React, { memo } from "react";
import { useSelector, useDispatch } from "react-redux";
// import { catsDataMemo, dogsDataMemo } from "./selectors";

// Components
const Dogs = memo(({ dogsData }) => {
  console.log("Dogs rendering..");
  return <h1>{dogsData}</h1>;
});

const Cats = memo(({ catsData }) => {
  console.log("Cats rendering..");
  return <h1>{catsData}</h1>;
});

function ReduxDeneme() {
  // Standart useSelector without MEMOIZED
  const dogsData = useSelector(
    state => state.pets.dogs,
    console.log("dogsData Selector çalıştı.")
  );
  const catsData = useSelector(
    state => state.pets.cats,
    console.log("catsData Selector çalıştı.")
  );

  // Actions

  const dispatch = useDispatch();
  const changeDogs = () => dispatch({ type: "CHANGE_DOGS" });
  const changeCats = () => dispatch({ type: "CHANGE_CATS" });

  const noChangeCats = () =>
    dispatch({ type: "NO_CHANGE_CATS", payload: catsData });

  return (
    <div>
      <Dogs dogsData={dogsData} />
      <Cats catsData={catsData} />
      <button onClick={changeDogs}>Change Dogs</button>
      <button onClick={changeCats}>Change CATS</button>
      <button onClick={noChangeCats}>No Change</button>
    </div>
  );
}

export default memo(ReduxDeneme);

Be Careful! When you click the "Change Dogs" button, output in the console will be:

dogsData Selector çalıştı.
catsData Selector çalıştı.
Dogs rendering..

or when you click the "Change Cats" button, the output will be:

dogsData Selector çalıştı.
catsData Selector çalıştı.
Cats rendering..

Whatever button you press, both of useSelectors will work as you can see from console.log

SCENARIO 2 - MEMOIZED WITH RESELECT MIDDLEWARE

Firstly, we separate memoized selectors to another file as @tudor.gergely mentioned.

BE CAREFULL! You must define the correct path of an object.

// selectors.js

import { createSelector } from "reselect";

export const dogsDataMemo = createSelector(
  state => state.pets.dogs, // BE CAREFULL while defining..
  dogs => {
    console.log("DogsDataMemo has worked.");
    return dogs;
  }
);
export const catsDataMemo = createSelector(
  state => state.pets.cats, // BE CAREFULL while defining..
  cats => {
    console.log("CatsDataMemo has worked.");
    return cats;
  }
);

Then, we import this file into main.js file and use again useSelector with our memoized selectors:

import React, { memo } from "react";
import { useSelector, useDispatch } from "react-redux";
import { catsDataMemo, dogsDataMemo } from "./selectors";

// Components
const Dogs = memo(({ dogsData }) => {
  console.log("Dogs rendering..");
  return <h1>{dogsData}</h1>;
});

const Cats = memo(({ catsData }) => {
  console.log("Cats rendering..");
  return <h1>{catsData}</h1>;
});

function ReduxDeneme() {
  const dogsData = useSelector(dogsDataMemo);
  const catsData = useSelector(catsDataMemo);

  // Actions
  const dispatch = useDispatch();
  const changeDogs = () => dispatch({ type: "CHANGE_DOGS" });
  const changeCats = () => dispatch({ type: "CHANGE_CATS" });

  const noChangeCats = () =>
    dispatch({ type: "NO_CHANGE_CATS", payload: catsData });

  return (
    <div>
      <Dogs dogsData={dogsData} />
      <Cats catsData={catsData} />
      <button onClick={changeDogs}>Change Dogs</button>
      <button onClick={changeCats}>Change CATS</button>
      <button onClick={noChangeCats}>No Change</button>
    </div>
  );
}

export default memo(ReduxDeneme);

and the final output:

  • When click the "Change Dogs" button:
DogsDataMemo has worked.
Dogs rendering
  • When click the "Change Cats" button:
CatsDataMemo has worked.
Cats rendering..

Upvotes: 1

hiddenuser.2524
hiddenuser.2524

Reputation: 4988

You are defining the selectors inside your component. You should do it outside (e.g. somewhere near your reducer).

Currently you are recreating the selector after each render. Here's a better way:

// inside reducer.js

  export const petsSel = state => state.pets;
  export const dogsDataMemo = createSelector(
    petsSel,
    pets => pets.dogs
  );
  export const catsDataMemo = createSelector(
    petsSel,
    pets => pets.cats
  );

Added a codesandbox with a working example based on your code: https://codesandbox.io/s/delicate-snowflake-5ssrw

To achieve what you desire you need to use React.memo (https://reactjs.org/docs/react-api.html#reactmemo) as well:

const Dogs = React.memo(({ dogsData }) => {
  console.log("Dogs rendering..");
  return <h1>{dogsData}</h1>;
});

Upvotes: 1

Related Questions