John John
John John

Reputation: 1465

Converting React Class based to Function based component

I am converting Class based to Function Based React Component but for some reason I am having trouble achieving the same result. I have a suspicion that it might be related to this.ref. Here is the code to let me know what I am doing wrong. Here is the link to codesandbox.

Here is the error I am getting: enter image description here

Class based component

import React, { Component } from 'react';

import { getSymbolData } from './utils.js';

import AsyncSelect from 'react-select/async';

class MyAsyncSelect extends Component {
  /* Select component reference can be used to get currently focused option */
  getFocusedOption() {
    return this.ref.select.select.state.focusedOption;
  }

  // we'll store lastFocusedOption as instance variable (no reason to use state)
  componentDidMount() {
    this.lastFocusedOption = this.getFocusedOption();
  }

  // Select component reference can be used to check if menu is opened */
  isMenuOpen() {
    return this.ref.select.state.menuIsOpen;
  }

  // This function will be called after each user interaction (click, keydown, mousemove).
  // If menu is opened and focused value has been changed we will call onFocusedOptionChanged
  // function passed to this component using props. We do it asynchronously because onKeyDown
  // event is fired before the focused option has been changed.
  onUserInteracted = () => {
    Promise.resolve().then(() => {
      const focusedOption = this.getFocusedOption();
      if (this.isMenuOpen() && this.lastFocusedOption !== focusedOption) {
        this.lastFocusedOption = focusedOption;
        this.props.onFocusedOptionChanged(focusedOption);
      }
    });
  };

  toggleClearable = () =>
    this.setState({ isClearable: !this.props.isClearable });

  onInputChange = (_, { action }) => {
    if (action === 'set-value') {
      this.props.onOptionSelected(this.getFocusedOption());
    }
  };

  loadOptions = async (inputText) => {
    const symbolData = await getSymbolData(inputText);
    console.log('symbolDATA: ', symbolData);
    return symbolData;
  };

  // we're setting onUserInteracted method as callback to different user interactions
  render() {
    console.log('RENDER Child');
    console.log('Props MyAsyncSelect', this.props);
    return (
      <div onMouseMove={this.onUserInteracted} onClick={this.onUserInteracted}>
        <AsyncSelect
          {...this.props}
          ref={(ref) => (this.ref = ref)}
          onKeyDown={this.onUserInteracted}
          onInputChange={this.onInputChange}
          loadOptions={this.loadOptions}
          autoFocus
          noOptionsMessage={() => 'Search symbol'}
          placeholder="Search Symbol"
          isClearable={this.props.isClearable} // allows us to clear the selected value either using the backspace button or the “x” button on the right side of the field
          clear // Removing all selected options using the clear button
          pop-value // Removing options using backspace
          loadingIndicator
        />
      </div>
    );
  }
}
export default MyAsyncSelect;

Function based component

import React, { useCallback, useEffect, useRef } from 'react';

import { getSymbolData } from './utils.js';

import AsyncSelect from 'react-select/async';

const MyAsyncSelect = (props) => {
  // let myRef = useRef();
  let myRef = React.createRef();
  const myRefLastFocusedOption = useRef();
  // let myRefLastFocusedOption = React.createRef();

  const getFocusedOption = useCallback(() => {
    console.log(myRef);
    // @ts-ignore
    return myRef.select.select.state.focusedOption;
  }, [myRef]);

  const isMenuOpen = () => {
    // @ts-ignore
    return myRef.select.state.menuIsOpen;
  };

  const onUserInteracted = () => {
    Promise.resolve().then(() => {
      const focusedOption = getFocusedOption();
      if (isMenuOpen() && myRefLastFocusedOption.current !== focusedOption) {
        myRefLastFocusedOption.current = focusedOption;
        props.onFocusedOptionChanged(focusedOption);
      }
    });
  };

  // const toggleClearable = () => setIsClearable(!isClearable);

  const onInputChange = (_, { action }) => {
    if (action === 'set-value') {
      props.onOptionSelected(getFocusedOption());
    }
  };

  const loadOptions = async (inputText) => {
    const symbolData = await getSymbolData(inputText);
    console.log('symbolDATA: ', symbolData);
    return symbolData;
  };

  useEffect(() => {
    myRefLastFocusedOption.current = getFocusedOption();
    // console.log('props', props);

    // return () => {
    //   cleanup;
    // };
  }, [getFocusedOption, myRefLastFocusedOption]);

  return (
    <div onMouseMove={onUserInteracted} onClick={onUserInteracted}>
      <AsyncSelect
        {...props}
        ref={(ref) => (myRef = ref)}
        // ref={myRef}
        onKeyDown={onUserInteracted}
        onInputChange={onInputChange}
        loadOptions={loadOptions}
        autoFocus
        noOptionsMessage={() => 'Search symbol'}
        placeholder="Search Symbol"
        isClearable={props.isClearable} // allows us to clear the selected value either using the backspace button or the “x” button on the right side of the field
        clear // Removing all selected options using the clear button
        pop-value // Removing options using backspace
        loadingIndicator
      />
    </div>
  );
};

export default MyAsyncSelect;

Upvotes: 0

Views: 84

Answers (1)

John John
John John

Reputation: 1465

Here is the working solution based on the sugestions from @DrewReese and @3limin4t0r

import React, { useCallback, useEffect, useRef } from 'react';

import { getSymbolData } from './utils.js';

import AsyncSelect from 'react-select/async';

const MyAsyncSelect = (props) => {
  const myRef = useRef();
  const myRefLastFocusedOption = useRef();

  const getFocusedOption = useCallback(() => {
    // @ts-ignore
    return myRef.current.select.select.state.focusedOption;
  }, [myRef]);

  const isMenuOpen = () => {
    // @ts-ignore
    return myRef.current.select.state.menuIsOpen;
  };

  const onUserInteracted = () => {
    Promise.resolve().then(() => {
      const focusedOption = getFocusedOption();
      if (isMenuOpen() && myRefLastFocusedOption.current !== focusedOption) {
        myRefLastFocusedOption.current = focusedOption;
        props.onFocusedOptionChanged(focusedOption);
      }
    });
  };

  const onInputChange = (_, { action }) => {
    if (action === 'set-value') {
      props.onOptionSelected(getFocusedOption());
    }
  };

  const loadOptions = async (inputText) => {
    const symbolData = await getSymbolData(inputText);
    console.log('symbolDATA: ', symbolData);
    return symbolData;
  };

  useEffect(() => {
    myRefLastFocusedOption.current = getFocusedOption();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div onMouseMove={onUserInteracted} onClick={onUserInteracted}>
      <AsyncSelect
        {...props}
        ref={myRef}
        onKeyDown={onUserInteracted}
        onInputChange={onInputChange}
        loadOptions={loadOptions}
        autoFocus
        noOptionsMessage={() => 'Search symbol'}
        placeholder="Search Symbol"
        isClearable={props.isClearable} // allows us to clear the selected value either using the backspace button or the “x” button on the right side of the field
        clear // Removing all selected options using the clear button
        pop-value // Removing options using backspace
        loadingIndicator
      />
    </div>
  );
};

export default MyAsyncSelect;

Upvotes: 3

Related Questions