Prevent `useSelector` from re-rendering component? - reactjs

I have a table with thousands of rows.
import React from "react"
import { useSelector } from "react-redux";
import { useEffect } from "react";
import Source from "../components/TranslationTable/Source";
const SourceContainer = React.memo((props) => {
const suggestionArray = useSelector((state) => state.suggestions.suggestionArray );
const isFetching = useSelector((state) => state.suggestions.isFetching);
let source = props.source;
if (props.shouldHighlight && !isFetching) {
//I will have code here to highlight text at some point
}
return (
<Source source={source}>{console.log("rendered")}</Source>
)
}, (prevProps, nextProps) => {
return true;
});
export default SourceContainer;
The above component is located inside the first column of each row. When the row is selected, props.shouldHighlight becomes true.
The problem that I have is that every single SourceContainer component re-renders every time a row is focused. When a row gets focused, isFetching becomes true in the redux state, and suggestionArray will become equal to an array of suggestions based upon the data in the selected row.
If I remove the if statement and the suggestionArray variable, the component STILL re-renders. Meaning, the only thing required to re-produce this issue is having an unused useSelector variable (ifFetching).
Can anyone explain how I can use useSelector without re-rendering the component when the state value of that selector changes?

Using useSelector, you add a dependency to your Component and it is normal that it re-renders if the content of useSelector changes.
The trick here is to make a less varying content for your useSelector function. Or at least something that changes only if the component needs to re-render
For exemple, from what I understand of what you are trying to achieve, your component should only change if "props.shouldHighlight && !isFetching" changes value.
If that's the case, then what you want to do is this :
const isFetching = useSelector((state) => (!state.suggestions.isFetching && props.shouldHighlight));

Related

React update useState() when variable value changes

How to update UseState() when variable 'matchUrl' changes. when navigating to another route the 'matchUrl' value changes. and the changes is reflected on console.log and on the 'h1' tag. but not the usestate(), it keeps the old value.
import React, {useState} from 'react'
import useInfinityScroll from './hooks/useInfinityScroll'
import names form './data/names'
function TableList({ match }) {
let matchUrl = match.params.name
const dataMatch = names.filter(name => name.occupation === matchUrl)
const [listNames, setListNames] = useState(dataMatch.slice(0, 10))
const [isFetching, setIsFetching] = useInfinityScroll(fetchMoreListItems) //fetches more items when scrolling to bottom of page
function fetchMoreListItems() {
setTimeout(() {
setListNames([...dataMatch.slice(0, 20)])
setIsFetching(false)
}, 2000)
}
return (
<>
<h1>{matchUrl}</h1>
<div>
{listNames.filter(name => name.occupation === matchUrl).map(listNames => <Table key={listNames.id} name={listNames} />)}
</div>
</>
)
}
export default TableList
useState hook takes initial value of the state as an argument, but it only assign the initial value to the state at the component mounting phase, not during re-rendering of the component if anything changes. Therefore, after initial rendering, state won't be changed even if the dataMatch variable changes.
Therefore, you need to have useEffect hook having dependency on dataMatch variable to keep track on its changes. Then you will be able to update the state when you've component re-rendering.
Use the following way to update the state.
useEffect(() => {
setListNames(dataMatch.slice(0, 10));
}, [dataMatch]);
For more information on useEffect hook, please refer https://reactjs.org/docs/hooks-effect.html
Hope this will solve your issue.
You haven't set the state for 'matchUrl'.Do the following modification to your code.
function TableList({ match }) {
let [matchUrl, setMatchUrl] = useState(match.params.name);
return (<><h1>{matchUrl}</h1></>);
}
Also when you change 'matchUrl' manually for any reason, you should use setMatchUrl('value') function to update the value
Here is the sample code. Hope this will help you 😇
import React, {useState, useEffect} from 'react'
function TableList({ match }) {
let matchUrl = match.params.name
useEffect(() => {
// YOUR_CODE_HERE WILL RUN WHEN the match.params.name is change
}, [match.params.name])
}
you can take a look also about documentation about useEffect

React useMemo firing when no change to Redux state detected

I have a state object in Redux and am using useSelector and deepEqual (from fast-deep-equal) to detect changes. Then useMemo on the current state. What's puzzling is that useMemo is being invoked even though the state seemingly hasn't changed.
Here is the structure of my code:
import deepEqual from "fast-deep-equal"
export function useMyState() {
const values = useSelector(
(store) => store.prop1,
(a, b) => { // comparison function arg to useSelector
const equal = deepEqual(a, b)
if (!equal) {
console.log("COMPARE FALSE")
}
return equal
}
)
useEffect(() => {
console.log("MOUNTED")
}, [])
return useMemo(() => {
console.log("VALUES CHANGED", JSON.stringify(values))
return {
values,
method1() {
// do something
}
}
}, [values])
}
I confirm that MOUNTED is only output once, and that VALUES CHANGED is output even when COMPARE FALSE is NOT output. How can this be...?
Maybe if component happens to be rendered as part of parent render it always gets the current object from store (which is not === the previous value) and useSelector compare fn is only used to prevent re-render of the component (in isolation) when state has not changed? If this is the case, what is the best-practice way to prevent useMemo being run when values are deep-equal?
codesandbox: https://codesandbox.io/s/redux-state-change-zdxzs
The issue occurs when there are two useSelector and one is triggered, causing a re-render. The component then thinks the result of the other useSelector has changed even though it has not according to the test in the selector itself (deepEqual).
To reproduce follow above link, click the increment button a few times - component is rendered but no change to todo items. Then click "re-create" button and increment again. Even though items have not changed according to deepEqual the text TODOS CHANGED! is output.
Thanks

Mixing Redux with useEffect Hook

I read that this is theoretically OK. In my small use case, I'm running into an issue, where mixing those technologies leads to double re-rendering.
First, when redux dispatch is executed and some components use a prop via useSelector. Then, after the functional component is already re-rendered, a useEffect hook is being applied which updates the state of some property. This update re-triggers the render again.
E.g. the below console log prints out twice in my case.
Question: should I remove the useEffect and useState and integrate it into redux' store?
import {useSelector} from "react-redux";
import React from "react";
const Dashboard = (props) => {
const selFilters = useSelector((state) => state.filter.selectedDashboardFilters);
const [columns, setColumns] = React.useState([]);
React.useEffect(() => {
let newColumns = getColumns();
setColumns(newColumns)
}, [selFilters]
)
console.log("RENDER")
return (
<h1>{columns.length}</h1>
)
}
If columns needs to be recomputed whenever selFilters changes, you almost certainly shouldn't be recomputing it within your component. If columns is computed from selFilters, then you likely don't need to store it as state at all. Instead, you could use reselect to create a getColumns() selector that derives the columns from the state whenever the relevant state changes. For example:
const getColumns = createSelector(
state => state.filter.selectedDashboardFilters,
selFilters => {
// Compute `columns` here
// ...
return columns
}
)

Redux state for particular component in a list

I'm learning react and redux. I'm creating a todo app but I'm facing a issue.
Let's say we have a list like
List.js
import React from 'react'
function List() {
return (
<div>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
</div>
)
}
export default List
Then we have
ListItem.js
import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
function ListItem() {
//This is some local state
const [isLoading, setLoading] = useState(false);
const loading = useSelector(state => state.loading);
// This is the loading flag which is coming from the reducer.. Which will be either true or false
useEffect(() => {
setLoading(true);
}, [loading]);
//Whenever the loading flag from the reducer changes, it changes the local state
return <div>{isLoading ? "loading" : "Hey, Im a list item"}</div>;
}
export default ListItem;
And let's say all listItem component have a click handler which dispatch an action to the change the loading flag in the reducer.
Now the issue I'm facing is as all the listItem are listening/subscribed to the same reducer, whenever the isLoading flag in the reducer changes, all the listItem start showing loading which is conditionally rendered from the return statement as shown above.
Expected Behaviour (or intended behaviour I must say)
I want to show loading on only that element that was clicked and whose local state was changed. But unfortunately my logic has a flaw that it all elements local states changes when the isLoading flag in the reducer changes
What you can do is provide key to each list item and assign loading to it in your reducer e.g
loading:{
List1: isLoading,
List2: notLoading
}

reactjs forfeit current component render

Scenario
Depending on the data coming into props I may want my component to render but I also may NOT want my component to rerender.
Code
Currently I'm using if(!props.data) return null but this merely sends back a blank component.
Question
How can I forfeit the render so that my current dom element stays unchanged, leaving the last rendered content intact? I'm wanting my component only update when props.data has a truthy value.
You can store the data received in a local state and only update it if props.data changes and has a truthy value like this:
import React, {useEffect, useState} from 'react';
const MyComponent = ({data}) => {
const [localData, setLocalData] = useState(data);
useEffect(() => {
if (data) setLocalData(data);
}, [data]);
return (
<div>{localData}</div>
);
};
But note that this has a little bit of a design smell. Why does your component receive new props in the first place if it shouldn't update. The correct place to hold that state may be in a component further up the tree.
The simple way is to try using conditional rendering of the component if the data is present in the parent instead of checking the props within the component.
function ParentComponent(props) {
const data = <your custom data>;
if(data) {
return <ChildComponent data={data} />;
}
}

Resources