Search code examples
reactjsspfxfluent-uifluentui-react

DetailsList is getting re-rendered and hence is loosing its state


So I have been trying to accomplish the idea that whenever the user clicks any row in the DetailsList the Button gets enabled and when the user clicks outside the selectionzone the Button gets disabled.

This is my code

import { DetailsList, SelectionMode, Selection, ISelection, initializeIcons, PrimaryButton } from '@fluentui/react'
import {useMemo } from 'react'
import { useBoolean } from '@uifabric/react-hooks'

interface ICurrency {
  type: string,
  amount: number
}
function App() {

  initializeIcons()
  const [isBtn, { setTrue: disableBtn, setFalse: enableBtn }] = useBoolean(true)
  const items: ICurrency[] = [
    {
      type: 'INR',
      amount: 20
    },
    {
      type: 'USD',
      amount: 50
    },
    {
      type: 'GBP',
      amount: 70
    }
  ]



  const selection: ISelection = useMemo(() => new Selection(
    {
      onSelectionChanged: ()=>{
        if(selection.getSelectedCount() > 0){
          enableBtn()
        }else{
          disableBtn()
        }
      }
    }
  ), [items])


  return (
    <div className="App">
      <PrimaryButton text="Button" disabled={isBtn}/>
      <DetailsList
        items={items} selectionMode={SelectionMode.single}
        selection={selection}
      />
    </div>
  );
}

export default App;

I even used useMemo and kept on banging my head but the problem persists where clicking any row the state is lost and the button is not enabled. I have already tried storing the state of selection also, count, everything but it seems I'm missing out on something essential or fundamental for the implementation


Solution

  • You need to memoize items because on each render it's being re-assigned and considered a new array which causes your selection to change because it relies on items as a useMemo dependency. On each state update the selection will reset.

    So one way you can fix this is by moving the items out of the function so that it holds reference instead of creating a new items array on each render.

    const items = [
      {
        type: "INR",
        amount: 20
      },
      {
        type: "USD",
        amount: 50
      },
      {
        type: "GBP",
        amount: 70
      }
    ];
    
    function App() {
      // code
    }
    

    or by using useMemo on those items:

    const items = useMemo(() =>  [
      {
        type: "INR",
        amount: 20
      },
      {
        type: "USD",
        amount: 50
      },
      {
        type: "GBP",
        amount: 70
      }
    ],[]);
    

    Also I see you have an error, the initializeIcons should only be called once. So that should probably be placed in useEffect:

    useEffect(() => {
      initializeIcons();
    },[])
    

    The final code sample should look like this:

    import {
      DetailsList,
      SelectionMode,
      Selection,
      ISelection,
      initializeIcons,
      PrimaryButton
    } from "@fluentui/react";
    import { useMemo, useEffect } from "react";
    import { useBoolean } from "@uifabric/react-hooks";
    
    const items = [
      {
        type: "INR",
        amount: 20
      },
      {
        type: "USD",
        amount: 50
      },
      {
        type: "GBP",
        amount: 70
      }
    ];
    
    function App() {
      useEffect(() => {
        initializeIcons();
      }, []);
    
      const [isBtn, { setTrue: disableBtn, setFalse: enableBtn }] = useBoolean(
        true
      );
    
      const selection = useMemo(
        () =>
          new Selection({
            onSelectionChanged: () => {
              if (selection.getSelectedCount() > 0) {
                enableBtn();
              } else {
                disableBtn();
              }
            }
          }),
        [items]
      );
    
      return (
        <div className="App">
          <PrimaryButton text="Button" disabled={isBtn} />
          <DetailsList
            items={items}
            selectionMode={SelectionMode.single}
            selection={selection}
          />
        </div>
      );
    }
    
    export default App;