Home > Net >  Most idiomatic way to only run React hook if its dependency changed from the last value
Most idiomatic way to only run React hook if its dependency changed from the last value

Time:12-23

I have a react app consisting of ParentComponent and HelpSearchWindow. On the page for ParentComponent, there is a button that lets you open up a window containing HelpSearchWindow. HelpSearchWindow has an input field and a search button. When an input is typed and search button is clicked, a search is run and results are displayed to the table on the window. The window can be closed. I have set up a react.useEffect() hook with the dependency [documentationIndexState.searchTerm] so that the search functionality is only run if the searchTerm changes.

However, the window was not behaving as I expected it to. Since useEffect() was being called every time the window was opened after it was closed, it would run the search again no matter if the searchTerm in the dependency array was the same. Because of this, I added another state prop (prevSearchTerm) from ParentComponent to store the last searched term. This way if the window is opened and closed multiple times without a new searchTerm being set, there is no repeat search run.

My question is, is there a more idiomatic/react-ish way to do this? Any other code formatting pointers are welcome as well

import {
    setSearchTerm,
} from 'documentationIndex.store';

interface Props {
    searchInput: string;
    setSearchInput: (searchInput: string) => void;
    prevSearchTerm: string;
    setPrevSearchTerm: (searchInput: string) => void;
}

export const HelpSearchWindow: React.FC<Props> = props => {
    const documentationIndexState = useSelector((store: StoreState) => store.documentationIndex);
    const dispatch = useDispatch();
    
    // Only run search if searchTerm changes
    React.useEffect(() => {
        async function asyncWrapper() {
            if (!documentationIndexState.indexExists) {
                // do some await stuff (need asyncWrapper because of this)
            }
            if (props.prevSearchTerm !== documentationIndexState.searchTerm) {
                // searching for a term different than the previous searchTerm so run search
                // store most recently used searchTerm as the prevSearchTerm
                props.setPrevSearchTerm(props.searchInput);
            }
        }
        asyncWrapper();
    }, [documentationIndexState.searchTerm]); 
    return (
        <input
            value={props.searchInput}
            onChange={e => props.setSearchInput(e.target.value)}
        />
        <button
            onClick={e => {
                e.preventDefault();
                dispatch(setSearchTerm(props.searchInput));
            }}
        >
            Search
        </button>
        <SearchTable
            rows={documentationIndexState.searchResults}
        />
    );
};

//--------- Parent Component----------------------------------------
const ParentComponent = React.memo<{}>(({}) => {
    const [searchInput, setSearchInput] = React.useState(''); // value inside input box
    const [prevSearchTerm, setPrevSearchTerm] = React.useState(''); // tracks last searched thing
    return(
        <HelpSearchWindow
            searchInput={searchInput}
            setSearchInput={setSearchInput}
            prevSearchTerm={prevSearchTerm}
            setPrevSearchTerm={setPrevSearchTerm}
        />
    );
});

CodePudding user response:

From the given context, the use of useEffevt hook is redundant. You should simply use a click handler function and attach with the button.

The click handler will store the search term locally in the component and also check if the new input value is different. If it is itll update state and make the api call.

CodePudding user response:

i assume you are using react-redux or react-redux/toolkit

'createSelector' is a utility function from the reselect library that allows you to create "memoized" selectors for efficient data retrieval in a Redux application. A memoized selector is a function that will return the same result for the same set of inputs, without recalculating the result if the inputs have not changed.

Here is an example of how you can use 'createSelector' in a Redux application:

import { createSelector } from 'reselect';

// Selectors
const getUsers = state => state.users;
const getUserId = (_, props) => props.userId;

// Memoized selector
export const getUser = createSelector(
  [getUsers, getUserId],
  (users, userId) => users.find(user => user.id === userId)
);

In this example, we have two selectors: getUsers and getUserId. getUsers selects the users slice of the state, and getUserId takes a second argument (the component's props) and returns the userId prop.

The createSelector function takes an array of input selectors and a transform function as arguments. The transform function receives the values of the input selectors as arguments and returns the transformed data.

In this case, the getUser selector is a memoized selector that returns the user object with the specified userId from the users array. If the users or userId values have not changed, getUser will return the same result without recalculating it.

You can then use the getUser selector in your component like this:

import { useSelector } from 'react-redux';
import { getUser } from './selectors';

function UserProfile(props) {
  const user = useSelector(state => getUser(state, props));
  return <div>{user.name}</div>;
}
  • Related