简体   繁体   中英

How to make one dropdown menu dependent on another

I have a dropdown menu showing states and counties. I want the county one to be dependent on the state one. I am using react, javascript, prisma to access the database. I made it work separated, so I can get the states to show and the counties, but I don't know how to make them dependent. What I think I need is a way to change my function that bring the county data. I can group by the state that was selected. So what I need is after getting the state that was selected to send that to my "byCounty" function. Is that possible?

menu.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>
    )
}

functions.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>

What I have:

在此处输入图像描述

Here's a self-contained example demonstrating how to "fetch" options from a mock API (async function), and use the results to render a top level list of options, using the selected one to do the same for a dependent list of options. The code is commented, and I can explain further if anything is unclear.

For simplicity, the example doesn't use states and counties, but the dependency relationship is the same.

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>

You could use custom hooks to do this.

The key is that in your code the second dropdown should watch the changes in the date of the first dropdown & react to these changes. In React you do this by using useEffect() (most of the times):

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

In the snippet,

  • The "states" API is querying a real source of data
  • I mocked the counties API (I couldn't find a free/freesource solution)
  • I added a simple cache mechanism for the counties, so the API doesn't get queried if the data has already been downloaded

 // 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>

The way you asked the question leads to different interpretations of your problem, both @muka.gergely 's and @jsejcksn 's answers are very good solutions but it's much more from what you really asked for. As you only want to get the value from selected state and fetch the counties from your backend, you can do the following:

functions.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,
     },
   })
 };

menu.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>
    )
}

And that's all, if you want to make the option county and state working together you can use the idea behind the other answers as well. Hope I helped you!

This is exactly what I'm wanting to do. . . For example, First drop down list would list all the State Names, then I click on that state, and it would generate a text file. The text file would be county name placefiles for all the counties for that state.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM