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.
I am fetching the users from dummyapi and i am listing them. There is a search input, i want to filter the users on the page by name. When i type the characters, it filters correctly. When i start to delete the character, users are not listed correctly. It remains filtered. How can i fix this ? This is my code:
import { useEffect, useState } from "react";
import Header from "../components/Header";
import User from "./User";
import axios from "axios";
function App() {
const BASE_URL = "https://dummyapi.io/data/api";
const APP_ID = "your app id";
const [users, setUsers] = useState(null);
const handleChange = (e) => {
const keyword = e.target.value.toLowerCase();
const filteredUsers =
users &&
users.filter((user) => user.firstName.toLowerCase().includes(keyword));
setUsers(filteredUsers);
};
useEffect(() => {
async function fetchData() {
try {
const response = await axios.get(`${BASE_URL}/user?limit=1`, {
headers: { "app-id": APP_ID },
});
setUsers(response.data.data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<>
<Header />
<div className="container">
<div className="filter">
<h3 className="filter__title">USER LIST</h3>
<div>
<input
id="filter"
type="text"
placeholder="Search by name"
onChange={handleChange}
/>
</div>
</div>
<div className="user__grid">
{users &&
users.map((user, index) => {
const { id } = user;
return <User key={index} id={id} />;
})}
</div>
</div>
</>
);
}
export default App;
This is because you are manipulating the original array of users. So after each filter the original array has less values than previous hence after deleting it will search from the reduced number of elements.
To avoid this, keep original way as it is, apply filter on that and store the result in a separate array.
Something like this:
const [allUsers, setAllUsers] = useState(null); //will store original records
const [users, setUsers] = useState(null); // will store filtered results
then in useEffect hook:
useEffect(() => {
async function fetchData() {
try {
const response = await axios.get(`${BASE_URL}/user?limit=1`, {
headers: { "app-id": APP_ID },
});
setUsers(response.data.data);
setAllUsers(response.data.data); //add this line
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
and finally in handleChange event:
const handleChange = (e) => {
const keyword = e.target.value.toLowerCase();
// use allUsers array (with original unchanged data)
const filteredUsers =
allUsers &&
allUsers.filter((user) => user.firstName.toLowerCase().includes(keyword));
setUsers(filteredUsers);
};
Obviously, you can use some better approach, but this is just to give the idea of original issue.
I am using firebase firestore and i fetched the data , everything is working fine but when i am passing it to some component only one item gets passed but log shows all the elements correctly.
I have just started learning react , any help is appreciated.
import React, { useEffect, useState } from 'react'
import { auth, provider, db } from './firebase';
import DataCard from './DataCard'
function Explore() {
const [equipmentList, setEquipments] = useState([]);
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
data.docs.forEach(item => {
setEquipments([...equipmentList, item.data()]);
})
}
useEffect(() => {
fetchData();
}, [])
equipmentList.forEach(item => {
//console.log(item.description);
})
const dataJSX =
<>
{
equipmentList.map(eq => (
<div key={eq.uid}>
{console.log(eq.equipment)}
<p>{eq.equipment}</p>
</div>
))
}
</>
return (
<>
{dataJSX}
</>
)
}
export default Explore
You have problems with setting fetched data into the state.
You need to call setEquipments once when data is prepared because you always erase it with an initial array plus an item from forEach.
The right code for setting equipment is
const fetchData = async () => {
const res = db.collection('Available');
const data = await res.get();
setEquipments(data.docs.map(item => item.data()))
}
I would like to use Ant Design AutoComplete component to fetch data from API. The component has the following code:
const Example = ({ token, service }) => {
const [value, setValue] = useState('')
const [options, setOptions] = useState([])
const { data } = useFetch(value)
const onSelect = (data) => {
console.log('onSelect', data)
}
const onChange = (query) => {
console.log("Search query ", query);
setValue(query);
console.log("State value ", value);
if (value && value.length > 1) {
setOptions(
data ? data : []
)
} else {
setOptions([])
}
}
return (
<AutoComplete
value={value}
options={options}
onSelect={onSelect}
onChange={onChange}
/>
)
}
data for the options is provided by useFetch(value) hook. So the value should be updated as the user types in the input.
But the problem is that value in the state is always one character behind the actual search query. Here is the link to my codesandbox. You can see that console.log() for search query and state value are always different. Is there any way to fix that? I need state value to be always in synch with the search query.
In this case you can make use of useEffect which can be used to calls API and update options.
You can do following:
React.useEffect(() => {
console.log("Value"); // This will be in sync with your search.
}, [value])
Working sandbox link: https://codesandbox.io/s/basic-usage-autocomplete-forked-eihbw?file=/index.js:369-448
My friend and I are working on a React translator app that uses the Lexicala API. We have two selector components: one for the source language and one for the target language. Users will first select the source language, and based on what they choose, a second dropdown menu will populate with a list of target languages that are available. Does anyone have any suggestions for how we would make the state update in the source language selector component affect the second component?
I'm including the code (without the comments) for each component. If you think we're already doing something incorrectly, please let me know.
SourceLanguageSelector.js
import React, {useState, useEffect} from 'react';
import { encode } from "base-64";
let headers = new Headers();
headers.append('Authorization', 'Basic ' + encode(process.env.REACT_APP_API_USERNAME + ":" + process.env.REACT_APP_API_PASSWORD));
const SourceLanguageSelector = () => {
const [loading, setLoading] = useState(true);
const [items, setItems] = useState([
{ label: "Loading...", value: "" }
]);
const [value, setValue] = useState();
useEffect(() => {
let unmounted = false;
async function getLanguages() {
const request = await fetch("https://dictapi.lexicala.com/languages", {
method: 'GET', headers: headers
});
const body = await request.json();
console.log(body);
const sourceLang = body.resources.global.source_languages;
const langName = body.language_names;
const compare = (sourceLanguage, languageName) => {
return sourceLanguage.reduce((obj, key) => {
if (key in languageName) {
obj[key] = languageName[key];
}
return obj;
}, {});
}
const sourceLanguageNames = compare(sourceLang, langName);
if (!unmounted) {
setItems(
Object.values(sourceLanguageNames).map((sourceLanguageName) => ({
label: sourceLanguageName,
value: sourceLanguageName
}))
);
setLoading(false);
}
}
getLanguages();
return () => {
unmounted = true;
}
}, []);
return (
<select
disabled={loading}
value={value}
onChange={e => setValue(e.currentTarget.value)}>
{items.map(item => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
);
};
export default SourceLanguageSelector;
TargetLanguageSelector.js
import React, { useState, useEffect } from 'react';
import { encode } from 'base-64';
let headers = new Headers();
headers.append('Authorization', 'Basic ' + encode(process.env.REACT_APP_API_USERNAME + ":" + process.env.REACT_APP_API_PASSWORD));
const TargetLanguageSelector = () => {
const [loading, setLoading] = useState(true);
const [items, setItems] = useState([
{ label: "Loading...", value: "" }
]);
const [value, setValue] = useState();
useEffect(() => {
let unmounted = false;
async function getLanguages() {
const request = await fetch("https://dictapi.lexicala.com/languages", {
method: 'GET', headers: headers
});
const body = await request.json();
console.log(body);
const targetLang = body.resources.global.target_languages;
const langName = body.language_names;
const compare = (targetLanguage, languageName) => {
return targetLanguage.reduce((obj, key) => {
if (key in languageName) {
obj[key] = languageName[key];
}
return obj;
}, {});
}
const targetLanguageNames = compare(targetLang, langName);
if (!unmounted) {
setItems(
Object.values(targetLanguageNames).map(target_languages =>
({
label: target_languages,
value: target_languages
}))
);
setLoading(false);
}
}
getLanguages();
return () => {
unmounted = true;
}
}, []);
return (
<select
disabled={loading}
value={value}
onChange={e => setValue(e.currentTarget.value)}>
{items.map(item => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
);
};
export default TargetLanguageSelector;
You can coordinate state between the selects through the parent. Pass a callback function to Source that gets called when it's onChange handler fires, passing the selected language up to the parent. In the parent, create a piece of state for the current selected language and pass it as a prop to Target. In target you can have a useEffect with that prop in the dependency array so whenever the selected language changes in Source, Target will make the appropriate api call.
You will have three options to have connection between two separate components.
Make parent as the connector between two children.
e.g, Use a function props to trigger handler in parent.
This handler will change the state of parent and it will lead to change the
props of another children like how #EvanMorrison has answered.
You can use a global state tree , a state management library like Redux, MobX or Flux which will help you manage a single source of truth. Therefore, when you dispatch an action in one component, the state can be detected at another component and then use an useEffect hook to trigger again using the state as a dependency.
Another pattern is to use Context API, but even for me, I rarely use it although understand the concepts.
You can find more explanation and examples here.
https://reactjs.org/docs/context.html#updating-context-from-a-nested-component
React.js - Communicating between sibling components