Search code examples
reactjstypescriptreact-contextreact-dropdown-tree-select

Prevent Re-Render of Functional Component Updating State in a Global Context


I have a Context that is being provided to my whole app. In the context is a state of arrays that hold keys to filter the data being shown on the app. I am using this dropdown selector which is a tree selector that takes JSON data and displays it. It has a example of how to prevent re-render when parent renders but I can't make it work for a functional component.

What is happening, when a selection is made in the dropdown it passes all currently selected to the onchange handler and in the handler it will set the state array in the context to the array passed by the dropdown. The state changes causes the dropdown component to re-render with the initial data passed by App as a prop resetting to having nothing checked.

I have looked at using React.memo to try and prevent the dropdown from re-rendering but can't get it to work. Preventing the re-render of the dropdown is plan A in solving this problem.

Global Context Code

import React, {useState} from 'react';

//Typing for global state
type globalStateObj = {
    Fleets: string[];
    updateFleets: (value: string[])=>void; 
}

//Context creation with initial
export const stateContext = React.createContext<globalStateObj>({
    Fleets: [""],
    updateFleets: ()=>{}
})

export const GlobalStateProvider = (props: {children: any}) =>{
    const [fleets, setFleets] = useState([""]);//states should match stateContext

    //Handlers for updating state
    const updateFleetHandler = (value: string[])=>{
        setFleets(value);
    }

    //Setting values to state and handlers
    const conextValue: globalStateObj = {
        Fleets: fleets,
        updateFleets: updateFleetHandler,
    }

    return(
        <stateContext.Provider value={conextValue}>
            {props.children}
        </stateContext.Provider>
    )
}; 

Dropdown Component code

import { stateContext } from "../GlobalState";
import './DropdownFilterBar.scss';
import "react-dropdown-tree-select/dist/styles.css";

interface DropdownFilterBar_Props{
    data: any[]
}
export const DropdownFilterBar = (props: DropdownFilterBar_Props) =>{
    const globalState = useContext(stateContext);
   const handleChange = (selected: any, allchecked: TreeNode[]) =>{
        let results = allchecked.map(({value})=>value);
        console.log(results);
        globalState.updateFleets(results);
    }

    const texts: TextProps = {
        placeholder:"Fleets",
        inlineSearchPlaceholder:"Search"
    }
    
    return(
        <div className="DropDownFilterBar">        
            <DropdownTreeSelect
                data={props.data}
                onChange={(selected, allchecked) =>{handleChange(selected, allchecked)}}
                className="fleet-selector"
                inlineSearchInput={true}
                texts={texts}
            />
        </div>
    )
};

App.tsx Where ContextProvider is situated

 <div className="App">
        <Container className="themed-container">
          <Row className='navRow'>
              <Col><TopNavBar/></Col>
          </Row>
          <GlobalStateProvider>
            <Row className="DropdownFilterRow">
              <Col><DropdownFilterBar data={DropdownFilterData}/></Col>
            </Row>
            <Row>
              <Col className="dashboard">
                <Routes>
                  <Route path="/overview" element={<Home/>} />
...

Solution

  • The solution I ended up using was the useMemo hook instead of the React.Memo. useContext forces a re-render even with React.Memo. I realized useMemo can be used to memorize the JSX element with a dependency on the props.data. The dropdown component code looks like this now

        const dropdown = useMemo(()=>{
             return(
                   <DropdownTreeSelect
                         data={props.data}
                         onChange={(selected, allchecked) =>{handleChange(selected, allchecked)}}
                         className="fleet-selector"
                         inlineSearchInput={true}
                         texts={texts}
                     />
              )}, [props.data]);
    
        return(
            <div className="DropDownFilterBar">        
                {dropdown}
            </div>
        )