background

In our project, there is such an input box component, which will go to the background to obtain data and provide options according to the input keyword. Every time the keyword changes, a request will be triggered, which is bound to cause a lot of pressure on the server. Of course, the trigger of input events inside and outside the component are more or less shaken proof processing. This time, the problem is that even if the event is triggered within the component, the parent component will re-render the child component during rerender if there is no caching or dependency processing for the function bound to the event

For example, there is a component called SearchInput

const SearchInput: React.FC<Props> = ({ onChange }) = > {
  const [params, updateParams] = React.useState(' ');

  function handleValueChange(value: string) {
    updateParams(value);
    onChange({ keyword: value });
  }

  return (
    <div>
      <input
        type="text"
        value={params}
        onChange={(e)= > handleValueChange(e.target.value)}
      />
    </div>
  );
};
Copy the code

We use SearchInput in this way in the parent component

function App() {
  const [params, updateParams] = React.useState({ keyword: ' ' });
  const [output, updateOutput] = React.useState(' ');

  const handleChange = (obj: any) = > {
    constnewParams = { ... obj }; updateParams(newParams); updateOutput(`keyword: ${newParams.keyword}`);
  };

  return (
    <div className="App">
      <SearchInput onChange={handleChange} />
      <div>{output}</div>
    </div>
  );
}
Copy the code

The body of the

In SearchInput, props. OnChange is used for props. If SearchInput is used for props

The function passed in will change

In SearchInput, do the following anti-shake on the props. OnChange passed in

import debounce from 'lodash/debounce';

const SearchInput: React.FC<Props> = ({ onChange }) = > {
  // omit some code...
  
  const debounced = React.useRef(debounce(onChange, 200));
  React.useEffect(() = > {
    debounced.current = debounce(onChange, 200);
    return debounced.current.cancel;
  }, [onChange]);
  
  function handleValueChange(value: string) {
    // ...
    debounced.current({ keyword: value });
  }
  
  // omit some code...
};
Copy the code

If the handleChange in the parent component is simple enough, this is the end of it. But our business often handles other states in this function or relies on other states to handle logic, and these complications cause props. OnChange to change

Caching event handlers

If the parent component’s handleChange contains other states, then frequent changes in props. OnChange will cause the useEffect callback in SelectInput to be triggered crazily, resulting in anti-shake failure and even state retention (e.g. Input a text, hold down the delete key to delete all the text, the last letter may be left in the output)

function App() {
  // omit some code...
  const [pager, setPager] = React.useState({});

  const handleChange = (obj: any) = > {
    constnewPager = { ... pager,current: 1 };
    setPager(newPager);
    // ...
  };

  // omit some code...
}
Copy the code

HandleChange can be cached in the parent component using useCallback so that the props. OnChange passed to SelectInput does not change frequently

function App() {
  // omit some code...

  const handleChange = React.useCallback((obj: any) = > {
    // ...} []);// omit some code...
}
Copy the code

Because handleChange doesn’t rely on the pager state to handle other logic, useCallback’s second argument can be passed an empty array, making the function cache only once when the component is mounted and never change again. But if you need to rely on the Pager state in handleChange, for example

function App() {
  // omit some code...
  
  const handleFetch = (newParams: any, newPager: any) = > {
    updateOutput(
      `keyword: ${newParams.keyword}, pager: ${newPager.current}.${newPager.size}`); setPager({ ... newPager,size: ~ ~ (100 * Math.random()) });
  };

  const handleChange = React.useCallback((obj: any) = > {
    // ...handleFetch(newParams, newPager); } []);// omit some code...
}
Copy the code

The second argument to useCallback is passed in an empty array, and the pager state that the function relies on does not change, and the handleFetch function that relies on the pager state does not get the latest argument. If the second parameter of useCallback is passed to [pager], it will change as frequently as if it were not cached.

Update status functionally

The easiest way to handle this is not to rely on any state handling logic in handleChange. Currently handleChange only relies on the Pager state, so we can retrieve the Pager state through a functional update of setPager

function App() {
  // omit some code...

  const handleChange = React.useCallback((obj: any) = > {
    setPager((pager: any) = > {
      constnewPager = { ... pager,current: 1 };
      const newParams = { ...obj };
      updateParams(newParams);
      handleFetch(newParams, newPager);
    });
  }, []);

  // omit some code...
}
Copy the code

Manage multiple states with useReducer

If handleChange relies on multiple states, you can use useReducer to consolidate all states and use Dispatch to update the state. React ensures that the identity of the Dispatch function is stable and does not change when the component is rerendered. So it’s safe to omit Dispatch from useEffect or useCallback’s dependency list.

function App() {
  const [state, dispatch] = React.useReducer(
    (state: any, action: any) = > {
      if (action.type === 'params') {
        return { ...state, params: {... state.params, ... action.params } }; }if (action.type === 'pager') {
        return { ...state, pager: {... state.pager, ... action.pager } }; }if (action.type === 'filter') {
        return { ...state, filter: {... state.filter, ... action.filter } }; }if (action.type === 'output') {
        return {
          ...state,
          output: `keyword: ${state.params.keyword}, pager: ${state.pager.current}.${state.pager.size}, filter: ${state.filter.pid}.${state.filter.id}`}; }return { ...state };
    },
    {
      params: { keyword: ' ' },
      pager: {},
      filter: {},
      output: ' '});const handleFetch = (newParams: any, newPager: any, newFilter: any) = > {
    dispatch({
      type: 'output'}); dispatch({type: 'pager'.pager: { ...newPager, size: ~ ~ (100 * Math.random()) },
    });
    dispatch({
      type: 'filter'.filter: { ...newFilter, id: ~ ~ (10000 * Math.random() + 10000)}}); };const handleChange = React.useCallback((obj: any) = > {
    const newFilter = { pid: 0 };
    const newPager = { current: 1 };
    constnewParams = { ... obj }; dispatch({type: 'params'.params: newParams });
    dispatch({ type: 'pager'.pager: newPager });
    dispatch({ type: 'filter'.params: newFilter }); handleFetch(newParams, newPager, newFilter); } []);return (
    <div className="App">
      <SearchInput onChange={handleChange} />
      <div>{state.output}</div>
    </div>
  );
}
Copy the code

conclusion

Simple to summarize, we in the use of the React Hooks handle events when binding to pay attention to the function of the incoming whether change, whether to have will depend on the status and processing logic, etc., according to the change of state in this paper, in dealing with image stabilization, for example introduces several situation and solution, of course, there are all sorts of other things in the business, I hope this article can give you some inspiration and hope that you can keep a calm mind when dealing with the problem