<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>React on Andrew Wilson's Blog</title><link>https://andrewilson.co.uk/tags/react/</link><description>Recent content in React on Andrew Wilson's Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 13 Feb 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://andrewilson.co.uk/tags/react/index.xml" rel="self" type="application/rss+xml"/><item><title>Fluent UI | React | Controlled multi-select Dropdown - Selected Keys</title><link>https://andrewilson.co.uk/post/2023/02/fluent-ui-react-controlled-multi-select-dropdown-selected-keys/</link><pubDate>Mon, 13 Feb 2023 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2023/02/fluent-ui-react-controlled-multi-select-dropdown-selected-keys/</guid><description>Problem Space I ran into a problem the other day working with the Fluent UI React component Controlled Multi-select Dropdown. As context, here is my redacted code and explanation:
import { Dropdown, IDropdownOption } from &amp;#39;office-ui-fabric-react&amp;#39;; // office-ui-fabric-react@7.204.0 export interface State { itemList: IDropdownOption[]; selectedItems: string[]; } export default class DropDownExample extends React.Component&amp;lt;any, State&amp;gt; { constructor(context) { super(context); this.state = { itemList: [{ key: &amp;#39;None&amp;#39;, text: &amp;#39;None&amp;#39; }], selectedItems: [] } this.</description><content:encoded><![CDATA[<h2 id="problem-space">Problem Space</h2>
<p>I ran into a problem the other day working with the Fluent UI React component <code>Controlled Multi-select Dropdown</code>. As context, here is my redacted code and explanation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">import</span> { Dropdown, IDropdownOption } <span style="color:#ff79c6">from</span> <span style="color:#f1fa8c">&#39;office-ui-fabric-react&#39;</span>; <span style="color:#6272a4">// office-ui-fabric-react@7.204.0
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"></span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">export</span> <span style="color:#ff79c6">interface</span> State {
</span></span><span style="display:flex;"><span>	itemList: <span style="color:#8be9fd">IDropdownOption</span>[];
</span></span><span style="display:flex;"><span>	selectedItems: <span style="color:#8be9fd">string</span>[];
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">export</span> <span style="color:#ff79c6">default</span> <span style="color:#ff79c6">class</span> DropDownExample <span style="color:#ff79c6">extends</span> React.Component&lt;<span style="color:#ff79c6">any</span>, <span style="color:#50fa7b">State</span>&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">constructor</span>(context) {
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">super</span>(context);
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">this</span>.state <span style="color:#ff79c6">=</span> {
</span></span><span style="display:flex;"><span>			itemList<span style="color:#ff79c6">:</span> [{ key<span style="color:#ff79c6">:</span> <span style="color:#f1fa8c">&#39;None&#39;</span>, text<span style="color:#ff79c6">:</span> <span style="color:#f1fa8c">&#39;None&#39;</span> }],
</span></span><span style="display:flex;"><span>			selectedItems<span style="color:#ff79c6">:</span> []
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">this</span>.GetItems();
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">this</span>.LoadPreselection();
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">private</span> <span style="color:#ff79c6">async</span> GetItems(){
</span></span><span style="display:flex;"><span>		<span style="color:#6272a4">// ... load items into itemList
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"></span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">private</span> <span style="color:#ff79c6">async</span> LoadPreselection(){
</span></span><span style="display:flex;"><span>		<span style="color:#6272a4">// ... load pre-selected items into selectedItems
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"></span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">public</span> onItemChange <span style="color:#ff79c6">=</span> (event: <span style="color:#8be9fd">React.FormEvent</span>&lt;<span style="color:#ff79c6">HTMLDivElement</span>&gt;, item: <span style="color:#8be9fd">IDropdownOption</span>)<span style="color:#ff79c6">:</span> <span style="color:#ff79c6">void</span> <span style="color:#ff79c6">=&gt;</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">if</span> (item) {
</span></span><span style="display:flex;"><span>      		<span style="color:#8be9fd;font-style:italic">let</span> tempItemsArray <span style="color:#ff79c6">=</span> selectedKeys;
</span></span><span style="display:flex;"><span>      		<span style="color:#ff79c6">const</span> index <span style="color:#ff79c6">=</span> tempItemsArray.indexOf(item.key, <span style="color:#bd93f9">0</span>); <span style="color:#6272a4">// Obtain item index if exists - returns -1 if it does not
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"></span>
</span></span><span style="display:flex;"><span>      		<span style="color:#ff79c6">if</span> (index <span style="color:#ff79c6">&gt;</span> <span style="color:#ff79c6">-</span><span style="color:#bd93f9">1</span>) {
</span></span><span style="display:flex;"><span>      		   tempItemsArray.splice(index, <span style="color:#bd93f9">1</span>); <span style="color:#6272a4">// If the item exists, remove the item from the existing array through the splice function
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"></span>			} <span style="color:#ff79c6">else</span> {
</span></span><span style="display:flex;"><span>      		  tempItemsArray.push(item.key); <span style="color:#6272a4">// If the item does not exist, add the item to the existing array
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"></span>      		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      		setSelectedKeys(tempItemsArray); <span style="color:#6272a4">// Update State
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"></span>    	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	render() {
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">return</span> (
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			&lt;<span style="color:#ff79c6">Dropdown</span>
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">placeholder</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;[Select Item]&#34;</span>
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">disabled</span><span style="color:#ff79c6">=</span>{<span style="color:#ff79c6">this</span>.state.itemList.length <span style="color:#ff79c6">===</span> <span style="color:#bd93f9">0</span>}
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">label</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;Items&#39;</span>
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">id</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;items&#39;</span>
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">responsiveMode</span><span style="color:#ff79c6">=</span>{<span style="color:#bd93f9">1</span>}
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">multiSelect</span>
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">options</span><span style="color:#ff79c6">=</span>{<span style="color:#ff79c6">this</span>.state.itemList}
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">selectedKeys</span><span style="color:#ff79c6">=</span>{<span style="color:#ff79c6">this</span>.state.selectedItems}
</span></span><span style="display:flex;"><span>				<span style="color:#50fa7b">onChange</span><span style="color:#ff79c6">=</span>{<span style="color:#ff79c6">this</span>.onItemChange.bind(<span style="color:#ff79c6">this</span>)}
</span></span><span style="display:flex;"><span>			/&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		);
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I had a list of items that I wanted to load into the Multi-select control from an API call. After obtaining the list of items, I wanted to preload any previously selected items.</p>
<p>At this point there were no problems&hellip;. until I had written my <code>onchange</code> method. For some reason I was able to manage the list of selected keys absolutely fine, but there were no visible changes to the UI selection when clicking.</p>
<p>For example:</p>
<ol>
<li>Open drop down.</li>
<li>Select an option.
<ul>
<li>onChange event triggered (state managed).</li>
<li>Selection in dropdown hasn&rsquo;t changed (Item UI does not confirm selection OR un-selection)</li>
</ul>
</li>
</ol>
<p>As you can imagine, this caused some confusion. The SelectedKeys Array has the correct values but the control is not confirming what I am seeing behind the scenes.</p>
<p>This is until the penny dropped when reviewing example solutions.</p>
<h2 id="solution">Solution</h2>
<p>The Dropdown control is treating the selectedKeys as an IMutable <em><code>&quot;(Once set, it cannot be changed)&quot;</code></em>. This completely changes the onChanged method solution.</p>
<p>Rather than <code>Push</code> to the <code>existing</code> array, we need to use the <code>Spread</code> syntax. The spread syntax will add the new item along with the already existing items into a <code>new array</code> that can be used by the control. Such as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...<span style="color:#ff79c6">this</span>.state.selectedItems, item.key <span style="color:#ff79c6">as</span> <span style="color:#8be9fd">string</span>
</span></span></code></pre></div><p>Rather than using <code>Splice</code> to remove an element from an <code>existing</code> array, we need to use the <code>Filter</code> method. The filter method creates a <code>new</code> array with all the elements that pass the logic test supplied, in this case bring everything over that doesn&rsquo;t match the selected item. Such as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">this</span>.state.selectedItems.filter(key <span style="color:#ff79c6">=&gt;</span> key <span style="color:#ff79c6">!==</span> item.key)
</span></span></code></pre></div><p>Combined, my <code>onChange</code> method now takes on the following form and operates as expected:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">public</span> onItemChange <span style="color:#ff79c6">=</span> (event: <span style="color:#8be9fd">React.FormEvent</span>&lt;<span style="color:#ff79c6">HTMLDivElement</span>&gt;, item: <span style="color:#8be9fd">IDropdownOption</span>)<span style="color:#ff79c6">:</span> <span style="color:#ff79c6">void</span> <span style="color:#ff79c6">=&gt;</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">if</span> (item) {
</span></span><span style="display:flex;"><span>			<span style="color:#ff79c6">this</span>.setState({
</span></span><span style="display:flex;"><span>				selectedItems:
</span></span><span style="display:flex;"><span>					<span style="color:#8be9fd">item.selected</span>
</span></span><span style="display:flex;"><span>						<span style="color:#ff79c6">?</span> [...<span style="color:#ff79c6">this</span>.state.selectedItems, item.key <span style="color:#ff79c6">as</span> <span style="color:#8be9fd">string</span>]
</span></span><span style="display:flex;"><span>						<span style="color:#ff79c6">:</span> <span style="color:#ff79c6">this</span>.state.selectedItems.filter(key <span style="color:#ff79c6">=&gt;</span> key <span style="color:#ff79c6">!==</span> item.key),
</span></span><span style="display:flex;"><span>			});
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	}
</span></span></code></pre></div>]]></content:encoded></item></channel></rss>