0

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

Ceroy
  • 181
  • 2
  • 11

2 Answers2

1

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;

Jonathan Portorreal
  • 2,730
  • 4
  • 21
  • 38
  • 1
    Thanks for the answer. I alternatively sorted the issue by storing the state of the items array. Also thank you for pointing out the initializeIcons() thing. – Ceroy Feb 26 '21 at 02:36
1

The Accepted answer has the proper reasoning for the issue. I just wanted to post my solution too. I just had to store the state of the items

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

interface ICurrency {
    type: string,
    amount: number
}

function App() {

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

    const [isBtn, { setTrue: disableBtn, setFalse: enableBtn }] = useBoolean(true)

    let _selection = new Selection({
        onSelectionChanged: () => {
            if (_selection.getSelectedCount() > 0) {
                enableBtn()
            } else {
                disableBtn()
            }
        }
    });

    let _initialItems: ICurrency[] = [
        {
            type: 'INR',
            amount: 20
        },
        {
            type: 'USD',
            amount: 50
        },
        {
            type: 'GBP',
            amount: 70
        }
    ]

    const [items, setItems] = useState(_initialItems)

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

export default App;

Now say if the items are coming from some props or some state management, then just use setItems inside a useEffect and set the dependency as that source

Ceroy
  • 181
  • 2
  • 11