reactjsselectavatar

React: How update children component hidden to show?


I have a problem that children component does not update hidden status when project is selected, it should then display all the the tasks included in selected projects. How ever when getTasks is done and it updates hidden state to false and it passes state to children component props but children component never reintialize select component and remains hidden. What I need to change to make my selectbox class RtSelect to display hidden state changes?

My master component:

import React, { useState, useEffect, useRef } from 'react';
import RtSelect from './RtSelect';
import api, { route } from "@forge/api";

function Projects() {
  const projRef = useRef();
  const taskRef = useRef();


  const [projects, setProjects] = useState(undefined)
  const [tasks, setTasks] = useState(undefined)
  const [projectid, setProjectid] = useState(undefined)
  const [taskid, setTaskid] = useState(undefined)
  const [hidden, setHidden] = useState(true)
  
  //haetaan atlasiansita projectit array

  useEffect(() => {
    let loadedProject = true;

    // declare the async data fetching function
    const fetchProjects = async () => {
      // get the data from the api
      const response = await api.asUser().requestJira(route`/rest/api/3/project`, {
        headers: {
          'Accept': 'application/json'
        }
      });
      const data = await response.json();
      //Mapataa hausta tarvittavat tiedot
      const result = data.map(function (item) {
        console.log('test');
        return [
          {
            label: item.name,
            value: item.id,
            avatar: item.avatarUrls['16x16']
          }
        ]
      })

      // set state with the result if `isSubscribed` is true
      if (loadedProject) {
        setProjects(result);
      }
    }
    //asetetaan state selectbox muutokselle

    // call the function
    fetchProjects()
      // make sure to catch any error
      .catch(console.error);;

    // cancel any future `setData`
    return () => loadedProject = false;
  }, [param])

  const getTasks = async (p) => {
    // get the data from the api
    const response = await api.asUser().requestJira(route`/rest/api/3/issuetype/project?projectId={p}`, {
      headers: {
        'Accept': 'application/json'
      }
    });
    const data = await response.json();
    //Mapataa hausta tarvittavat tiedot
    const result = data.map(function (item) {
      console.log('test');
      return [
        {
          value: item.id,
          label: item.description,
          avatar: item.iconUrl
        }
      ]
    })
    setTasks(result)
    setHidden(false)
  }

  useEffect(() => {
    projRef.current.addEventListener("onChange", (e) => {
      setProjectid(e.target.value)
      console.log("Project select boxin arvo on: " + e.target.value);
      getTasks(projectid)
    });
  });

  useEffect(() => {
    taskRef.current.addEventListener("onChange", (e) => {
      setTaskid(e.target.value)
      console.log("Select task boxin arvo on: " + e.target.value);
    });
  });
  
  return (
    <div>
      <div className='projects'>
        <RtSelect info="Choose project:" options={projects} hidden={false} ref={projRef} />
      </div>
      <div className='tasks'>
        <RtSelect info="Choose Task:" options={tasks} hidden={hidden} ref={taskRef} />
      </div>
    </div>
  );
}

export default Projects

Here is my RtSelect class code:

import React from "react";
import Select from "react-select";

class RtSelect extends React.Component {
  state = {
    info: this.props.info,
    options: this.props.options,
    hidden: this.props.hidden,
    menuIsOpen: '',
    menuWidth: "",
    IsCalculatingWidth: ''
  };

  constructor(props) {
    super(props);
    this.selectRef = props.ref
   
    this.onMenuOpen = this.onMenuOpen.bind(this);
    this.setData = this.setData.bind(this);
  }
  componentDidMount() {
    if (!this.state.menuWidth && !this.state.isCalculatingWidth) {
      setTimeout(() => {
        this.setState({IsCalculatingWidth: true});
        // setIsOpen doesn't trigger onOpenMenu, so calling internal method
        this.selectRef.current.select.openMenu();
        this.setState({menuIsOpen: true});
        
      }, 1);
    }
  }

  onMenuOpen() {
    if (!this.state.menuWidth && this.state.IsCalculatingWidth) {
      setTimeout(() => {
        const width = this.selectRef.current.select.menuListRef.getBoundingClientRect()
          .width;
        this.setState({menuWidth: width});
        this.setState({IsCalculatingWidth: false});

        // setting isMenuOpen to undefined and closing menu
        this.selectRef.current.select.onMenuClose();
        this.setState({menuIsOpen: undefined});
      }, 1);
    }
  }

  styles = {
    menu: (css) => ({
      ...css,
      width: "auto",
      ...(this.state.IsCalculatingWidth && { height: 0, visibility: "hidden" })
    }),
    control: (css) => ({ ...css, display: "inline-flex " }),
    valueContainer: (css) => ({
      ...css,
      ...(this.state.menuWidth && { width: this.state.menuWidth })
    })
  };
  setData (props) {
    if (props.info) {
      this.setState({
        info: props.info 
      })
    }
    if (props.options) {
      this.setState({
        options: props.options
      })
    }
    if (props.hidden) {
      this.setState({
        hidden: props.hidden
      })
    }
  }
  render () {
    return (
      <div style={{ display: "flex" }}>
        <div style={{ margin: "8px" }}>{this.state.info}</div>
        <div style={{minWidth: "200px"}}>
          <Select
            ref={this.selectRef}
            onMenuOpen={this.onMenuOpen}
            options={this.state.options}
            menuIsOpen={this.state.menuIsOpen}
            styles={this.styles}
            isDisabled={this.state.hidden}
            formatOptionLabel={(options) => (
              <div className="select-option" style={{ display: "flex", menuWidth: "200px"}}>
                <div style={{ display: "inline", verticalAlign: "center" }}>
                  <img src={options.avatar} width="30px" alt="Avatar" />
                </div>
                &nbsp;
                <div style={{ display: "inline", marginLeft: "10px" }}>
                  <span>{options.label}</span>
                </div>
              </div>
            )}
          />
        </div>
        </div>
    );
  }
}

export default RtSelect;

Solution

  • Ok I found from other examples that I can use the ref to acces child method so here is they way to update component:

    useEffect(() => {
        projRef.current.addEventListener("onChange", (e) => {
          setProjectid(e.target.value)
          console.log("Project select boxin arvo on: " + e.target.value);
          getTasks(projectid)
          //Using RtSelect taskRef to locate children component method to update component
          taskRef.current.setData({hidden: false})
        });
      });