【问题标题】:How to make one dropdown menu dependent on another如何使一个下拉菜单依赖于另一个
【发布时间】:2022-10-18 14:44:58
【问题描述】:

我有一个显示州和县的下拉菜单。我希望县一依赖于州一。 我正在使用 react、javascript、prisma 来访问数据库。 我让它分开工作,所以我可以让州和县展示,但我不知道如何让它们依赖。 我认为我需要的是一种方法来改变我带来县数据的功能。我可以按被选中的状态分组。所以我需要的是在获得被选择发送到我的“byCounty”函数的状态之后。那可能吗?

菜单.js

export default function DropDownMenu(props){
    if(!props.states) return
    return(
        <table>
            <body>
            <select onChange={(e) => { console.log(e.target.value) }}>
                {props.states.map(states=>
                    <option>{states.state}</option>
                )}
            </select>
            <select >
                {props.byCounty.map(byCounty=>
                    <option>{byCounty.county}</option>
                )}
            </select>
            </body>
        </table>
    )
}

函数.js

const states = await prisma.county.groupBy({
        by:["state"],
        where: {
            date: dateTime,
        },
        _sum:{
            cases:true,
        },
    });

 const byCounty = await prisma.county.groupBy({
        by:["county"],
        where: {
            date: dateTime,
            state: 'THIS SHOULD BE THE STATE NAME SELECTED BY USER'
        },
        _sum:{
            cases:true,
        },
    });

const result =JSON.stringify(
        {states:states, byCounty:byCounty},
        (key, value) => (typeof value === 'bigint' ? parseInt(value) : value) // return everything else unchanged
      )
    res.json(result);

index.js

<div className={styles.table_container}>
                    <h2>Teste</h2>
                    <DropDownMenu states={myData?myData.states:[]} byCounty={myData?myData.byCounty:[]}></DropDownMenu>
              </div>

我有的:

【问题讨论】:

    标签: javascript reactjs drop-down-menu prisma


    【解决方案1】:

    这是一个独立的示例,演示如何从模拟 API(异步函数)“获取”选项,并使用结果呈现顶级选项列表,使用选定的选项对依赖的选项列表执行相同操作。代码已注释,如果有任何不清楚的地方,我可以进一步解释。

    为简单起见,该示例没有使用州和县,但依赖关系是相同的。

    TS Playground

    body { font-family: sans-serif; }
    .select-container { display: flex; gap: 1rem; }
    select { font-size: 1rem; padding: 0.25rem; }
    <div id="root"></div><script src="https://unpkg.com/react@18.1.0/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@18.1.0/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.17.10/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
    <script type="text/babel" data-type="module" data-presets="tsx,react">
    
    // import * as ReactDOM from 'react-dom/client';
    // import {
    //   type Dispatch,
    //   type ReactElement,
    //   type SetStateAction,
    //   useEffect,
    //   useRef,
    //   useState,
    // } from 'react';
    
    // This Stack Overflow snippet demo uses UMD modules instead of the above import statments
    const {
      useEffect,
      useRef,
      useState,
    } = React;
    
    // The next section is just a mock API for getting dependent options (like your States/Counties example):
    
    async function getOptionsApi (level: 1): Promise<string[]>;
    async function getOptionsApi (
      level: 2,
      level1Option: string,
    ): Promise<string[]>;
    async function getOptionsApi (
      level: 1 | 2,
      level1Option?: string,
    ) {
      const OPTIONS: Record<string, string[]> = {
        colors: ['red', 'green', 'blue'],
        numbers: ['one', 'two', 'three'],
        sizes: ['small', 'medium', 'large'],
      };
    
      if (level === 1) return Object.keys(OPTIONS);
      else if (level1Option) {
        const values = OPTIONS[level1Option];
        if (!values) throw new Error('Invalid level 1 option');
        return values;
      }
    
      throw new Error('Invalid level 1 option');
    }
    
    // This section includes the React components:
    
    type SelectInputProps = {
      options: string[];
      selectedOption: string;
      setSelectedOption: Dispatch<SetStateAction<string>>;
    };
    
    function SelectInput (props: SelectInputProps): ReactElement {
      return (
        <select
          onChange={(ev) => props.setSelectedOption(ev.target.value)}
          value={props.selectedOption}
        >
          {props.options.map((value, index) => (
            <option key={`${index}.${value}`} {...{value}}>{value}</option>
          ))}
        </select>
      );
    }
    
    function App (): ReactElement {
      // Use a ref to track whether or not it's the initial render
      const isFirstRenderRef = useRef(true);
    
      // State for storing the top level array of options
      const [optionsLvl1, setOptionsLvl1] = useState<string[]>([]);
      const [selectedLvl1, setSelectedLvl1] = useState('');
    
      // State for storing the options that depend on the selected value from the level 1 options
      const [optionsLvl2, setOptionsLvl2] = useState<string[]>([]);
      const [selectedLvl2, setSelectedLvl2] = useState('');
    
      // On the first render only, get the top level options from the "API"
      // and set the selected value to the first one in the list
      useEffect(() => {
        const setOptions = async () => {
          const opts = await getOptionsApi(1);
          setOptionsLvl1(opts);
          setSelectedLvl1(opts[0]!);
        };
    
        if (isFirstRenderRef.current) {
          isFirstRenderRef.current = false;
          setOptions();
        }
      }, []);
    
      // (Except for the initial render) every time the top level option changes,
      // get the dependent options from the "API" and set
      // the selected dependent value to the first one in the list
      useEffect(() => {
        const setOptions = async () => {
          const opts = await getOptionsApi(2, selectedLvl1);
          setOptionsLvl2(opts);
          setSelectedLvl2(opts[0]!);
        };
    
        if (isFirstRenderRef.current) return;
        setOptions();
      }, [selectedLvl1]);
    
      return (
        <div>
          <h1>Dependent select options</h1>
          <div className="select-container">
            <SelectInput
              options={optionsLvl1}
              selectedOption={selectedLvl1}
              setSelectedOption={setSelectedLvl1}
            />
            <SelectInput
              options={optionsLvl2}
              selectedOption={selectedLvl2}
              setSelectedOption={setSelectedLvl2}
            />
          </div>
        </div>
      );
    }
    
    const reactRoot = ReactDOM.createRoot(document.getElementById('root')!)
    reactRoot.render(<App />);
    
    </script>

    【讨论】:

      【解决方案2】:

      您可以使用自定义挂钩来执行此操作。

      关键是,在您的代码中,第二个下拉列表应该观察第一个下拉列表日期的变化并对这些变化做出反应。在 React 中,您可以使用 useEffect() 来做到这一点(大多数时候):

      useEffect(() => {
        reactingToChanges()
      }, [watchedVariable])
      

      在sn-p中,

      • “状态”API 正在查询真实的数据源
      • 我嘲笑了县 API(我找不到免费/免费资源的解决方案)
      • 我为县添加了一个简单的缓存机制,因此如果数据已经下载,则不会查询 API

      // THE IMPORTANT PART IS IN A COMMENT TOWARDS THE BOTTOM
      
      const { useEffect, useState } = React;
      
      const useFetchStates = () => {
        const [states, setStates] = useState([]);
      
        const fetchStates = () => {
          const myHeaders = new Headers();
          myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
      
          const urlencoded = new URLSearchParams();
          urlencoded.append("iso2", "US");
      
          const requestOptions = {
            method: "POST",
            headers: myHeaders,
            body: urlencoded,
            redirect: "follow"
          };
      
          fetch(
            "https://countriesnow.space/api/v0.1/countries/states",
            requestOptions
          )
            .then((response) => response.json())
            .then(({ data: { states } }) => setStates(states))
            .catch((error) => console.log("error", error));
        };
      
        if (!states.length) {
          fetchStates();
        }
      
        return {
          states
        };
      };
      
      const useFetchCounties = () => {
        const [countiesByState, setCountiesByState] = useState({});
        const [counties, setCounties] = useState([]);
      
        const fetchCounties = (state) => {
          if (state in countiesByState) {
            setCounties(countiesByState[state]);
          } else if (state) {
            fetch("https://jsonplaceholder.typicode.com/todos")
              .then((response) => response.json())
              .then((json) => {
                const mappedCounties = json.map(({ id, title }) => ({
                  id: `${state}-${id}`,
                  title: `${state} - ${title}`
                }));
                setCounties(mappedCounties);
                setCountiesByState((prevState) => ({
                  ...prevState,
                  [state]: mappedCounties
                }));
              });
          } else {
            setCounties([]);
          }
        };
      
        return {
          counties,
          fetchCounties
        };
      };
      
      const Selector = ({ options = [], onChange, dataType }) => {
        return (
          <select onChange={(e) => onChange(e.target.value)} defaultValue={"DEFAULT"}>
            <option disabled value="DEFAULT">
              SELECT {dataType}
            </option>
            {options.map(({ name, val }) => (
              <option key={val} value={val}>
                {name}
              </option>
            ))}
          </select>
        );
      };
      
      const App = () => {
        const { states = [] } = useFetchStates();
        const [selectedState, setSelectedState] = useState("");
        const { counties, fetchCounties } = useFetchCounties();
        const [selectedCounty, setSelectedCounty] = useState("");
      
        // here's the heart of this process, the useEffect():
        // when the selectedState variable changes, the
        // component fetches the counties (based on currently
        // selected state) and resets the currently selected
        // county (as we do not know that at this time)
        useEffect(() => {
          fetchCounties(selectedState);
          setSelectedCounty("");
        }, [selectedState]);
      
        const handleSelectState = (val) => setSelectedState(val);
        const handleSelectCounty = (val) => setSelectedCounty(val);
        
        return (
          <div>
            <Selector
              options={states.map(({ name, state_code }) => ({
                name,
                val: state_code
              }))}
              onChange={handleSelectState}
              dataType={"STATE"}
            />
            <br />
            <Selector
              options={counties.map(({ id, title }) => ({
                name: title,
                val: id
              }))}
              onChange={handleSelectCounty}
              dataType={"COUNTY"}
            />
            <br />
            Selected state: {selectedState}
            <br />
            Selected county: {selectedCounty}
          </div>
        );
      };
      const root = ReactDOM.createRoot(document.getElementById("root"));
      root.render(<App />);
      <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
      <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
      
      <div id="root"></div>

      【讨论】:

      • 假设这段代码在我的 menu.js 文件中。我将如何包含在我的 index.js 文件中?
      • @studentpr 默认导出App,然后就是一个组件
      【解决方案3】:

      您提出问题的方式导致对您的问题的不同解释,@muka.gergely@jsejcksn 的答案都是非常好的解决方案,但它比您真正要求的要多得多。由于您只想从选定状态获取值并从后端获取县,您可以执行以下操作:

      函数.js

       // change to a function that gets a state as parameter
       const byCounty = async (selectedState) => { 
         return await prisma.county.groupBy({
           by:["county"],
           where: {
             date: dateTime,
             // use the received parameter here to fetch the counties
             state: selectedState
           },
           _sum:{
             cases:true,
           },
         })
       };
      

      菜单.js

      export default function DropDownMenu(props){
          if(!props.states) return
          return(
              <table>
                  <body>
                  <select 
                    // use the byCounty function with the selected value to fetch the counties
                    onChange={ async (e) => { 
                      await byCounty(e.target.value) 
                    }}
                  >
                      {props.states.map(states=>
                          <option>{states.state}</option>
                      )}
                  </select>
                  <select >
                      {props.byCounty.map(byCounty=>
                          <option>{byCounty.county}</option>
                      )}
                  </select>
                  </body>
              </table>
          )
      }
      

      仅此而已,如果您想让选项县和州一起工作,您也可以使用其他答案背后的想法。希望我对你有所帮助!

      【讨论】:

      • 这正是我需要的。但是,我在这部分出现错误“{props.byCounty.map(byCounty=>”它说“TypeError: Cannot read properties of undefined (reading 'map')”
      • 我通过添加问号“byCounty?.map”来修复此错误,但现在当我选择状态时,我得到“ReferenceError:byCounty 未定义”
      • 是json。 const result =JSON.stringify( {states:states,byCounty:byCounty}, (key, value) => (typeof value === 'bigint' ? parseInt(value) : value) // 其他所有内容保持不变 ) res. json(结果);
      • 您是否在 menu.js 中导入了 byCounty 函数?
      【解决方案4】:

      如果国家/地区状态 https://www.npmjs.com/package/multi-nested-select 的嵌套多选,使用此软件包将解决所有问题

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-08
        • 1970-01-01
        • 1970-01-01
        • 2020-11-13
        • 1970-01-01
        • 2019-09-05
        • 1970-01-01
        相关资源
        最近更新 更多