How to make one dropdown menu dependent on another - javascript

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.

Related

Why the filter does not return the list on the initial render?

What I have is a list that was fetched from an api. This list will be filtered based on the input. But at the first render it will render nothing, unless I press space or add anything to the input. Another solution is set the fetched data to the filteredList. But I don't know if it is the right thing to set the fetched data to two arrays.
import React, { useState, useEffect } from "react";
const PersonDetail = ({ person }) => {
return (
<div>
Id: {person.id} <br />
Name: {person.name} <br />
Phone: {person.phone}
</div>
);
};
const App = () => {
const [personsList, setPersonsList] = useState([]);
const [personObj, setPersonObj] = useState({});
const [showPersonDetail, setShowPersonDetail] = useState(false);
const [newPerson, setNewPerson] = useState("");
const [filter, setFilter] = useState("");
const [filteredList, setFilteredList] = useState(personsList);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => {
setPersonsList(data);
//setFilteredList(data) <-- I have to add this to work
console.log(data);
});
}, []);
const handleClick = ({ person }) => {
setPersonObj(person);
if (!showPersonDetail) {
setShowPersonDetail(!showPersonDetail);
}
};
const handleChange = (event) => {
setNewPerson(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
const tempPersonObj = {
name: newPerson,
phone: "123-456-7890",
id: personsList.length + 1,
};
setPersonsList((personsList) => [...personsList, tempPersonObj]);
//setFilteredList(personsList) <-- to render the list again when add new person
setNewPerson(" ");
};
const handleFilter = (event) => {
setFilter(event.target.value);
const filteredList =
event.target.value.length > 0
? personsList.filter((person) =>
person.name.toLowerCase().includes(event.target.value.toLowerCase())
)
: personsList;
setFilteredList(filteredList);
};
return (
<div>
<h2>List:</h2>
Filter{" "}
<input value={filter} onChange={handleFilter} placeholder="Enter" />
<ul>
{filteredList.map((person) => {
return (
<li key={person.id}>
{person.name} {""}
<button onClick={() => handleClick({ person })}>View</button>
</li>
);
})}
</ul>
<form onSubmit={handleSubmit}>
<input
placeholder="Add Person"
value={newPerson}
onChange={handleChange}
/>
<button type="submit">Add</button>
</form>
{showPersonDetail && <PersonDetail person={personObj} />}
</div>
);
};
export default App;
Your filtered list is actually something derived from the full persons list.
To express this, you should not create two apparently independent states in this situation.
When your asynchronous fetch completes, the filter is probably already set and you are just setting personsList which is not the list you are rendering. You are rendering filteredList which is still empty and you are not updating it anywhere, except when the filter gets changed.
To avoid all of this, you could create the filtered list on each rendering and — if you think this is not efficient enough — memoize the result.
const filteredList = useMemo(() =>
filter.length > 0
? personsList.filter((person) =>
person.name.toLowerCase().includes(filter.toLowerCase())
)
: personsList,
[filter, personsList]
);
When the filter input gets changed, you should just call setFilter(event.target.value).
This way, you will always have a filtered list, independent of when your asynchronous person list fetching completes or when filters get updated.
Side note: Writing const [filteredList, setFilteredList] = useState(personsList); looks nice but is the same as const [filteredList, setFilteredList] = useState([]); because the initial value will be written to the state only once, at that's when the component gets initialized. At that time personsList is just an empty array.

onSearch event on React Multiselect Dropdown?

I have used the multiselect-react-dropdown library for implement MultiSelect dropdown. I this library have two props: one is options and second is onSearch. In options props passed the data which is showing on dropdown. user can select the data from it. But onSearch is used on search user's by api. So how can i manage both the data on dropdown. Please help me to solve this.
<Multiselect
onSearch={(e) => callSearchApi(e)}
placeholder="Select Contributor"
onSelect={onSelect}
closeIcon="circle"
options={UsersData}
displayValue="name"
/>
API Call
const callSearchApi = (name) => {
console.log('callSearchApi', name);
collaboratorsSearch(name)
.then((response) => {
console.log('callSearchApi fetch user', response.data.results);
})
.catch((error) => {
console.log('callSearchApi fetch user Error', error);
});
};
To implement this feature you need to use react hook useState here.
You need to set below state and hooks. I am setting options inside useEffect here but in your case you can set it in button click event.
const [ selectedOptionValue, setSelectedOptionValue ] = useState('');
const [ options, setOptions ] = useState([]);
useEffect(() => {
setOptions(Data);
}, [])
You can toggle options data based on your requirement. Set this options variable either from callSearchApi or from props.
I have implemented onSearch and callSearchApi function here. Just to reduce the code I am using async and await.
const callSearchApi = async (value) => {
const response = await fetch('https://jsonplaceholder.typicode.com/users/?name='+ value);
const users = await response.json();
if (users && users.length > 0) {
setOptions(users);
} else {
setOptions(Data);
}
};
const onSearch = (value) => {
console.log('Value', value)
callSearchApi(value);
}
Please note I have used dummy API here which gives users information.
Here is the complete code.
import React, { useState, useEffect } from "react";
import "./style.css";
import Multiselect from 'multiselect-react-dropdown';
import { Data } from "./data";
export default function App() {
const [ selectedOptionValue, setSelectedOptionValue ] = useState('');
const [ options, setOptions ] = useState([]);
useEffect(async () => {
setOptions(Data);
}, [])
const onSelect = (selectedList, selectedItem) => {
}
const onRemove = (selectedList, removedItem) => {
}
const callSearchApi = async (value) => {
const response = await fetch('https://jsonplaceholder.typicode.com/users/?name='+ value);
const users = await response.json();
if (users && users.length > 0) {
setOptions(users);
} else {
setOptions(Data);
}
};
const onSearch = (value) => {
console.log('Value', value)
callSearchApi(value);
}
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
{
// options? options.length : null
options? ( <Multiselect
onSearch={onSearch}
options={options} // Options to display in the dropdown
selectedValues={selectedOptionValue} // Preselected value to persist in dropdown
onSelect={onSelect} // Function will trigger on select event
onRemove={onRemove} // Function will trigger on remove event
displayValue="name" // Property name to display in the dropdown options
/>): null
}
</div>
);
}
You can find working example here on stackblitz.

I'm trying to filter an array of Objects simultaneosly by number and by string value

i have a little problem in my App !
it's a simple App that shows some restaurant tables and let you filter these tables by number of people eating (capacity) and the preferred place (Inside, Patio, Bar)
I have a problem specifically with this filter functionality:
i'm able to filter the tables by Place, but when i try to simultaneosly filter the Tables by Capacity it won't work (it'll forget the Place filter, and just filter them by Capacity).
I'll explain the user flow i used:
I click on the Place select and choose the option 'Patio'
At this point 2 tables are showed; one is for 5 people, the other for 9 people
i click on the Capacity filter, choosing a table for 9 persons.
the App will forget the 'Patio' value and it'll also show a table with Location ' Bar' (and that's the part i'm trying to fix, i want the App to remember 'Patio' and only showing that one)
const defaultState : any = {
tables: [],
tablesFiltered: [],
bookings: [],
error: null,
loading: false,
}
case FILTER_TABLE:
return {
...state,
tablesFiltered: action.payload,
}
this is my action
export const filterTables = (filteredTable: tableI[]) => {
return (dispatch: (arg0: { type: string; payload?: unknown; }) => void) =>
dispatch({type: FILTER_TABLE, payload: filteredTable})
}
finally, the component where the logic of the filter is:
TableFilter.tsx
import { Form } from 'react-bootstrap';
import { useSelector, useDispatch } from 'react-redux';
import { filterTables } from '../../store/actions';
import { tableI } from '../../Interfaces';
const TableFilter: React.FC = () => {
const tables: tableI[] = useSelector((state: any) => state.tables.tables);
const dispatch = useDispatch();
const handleChange = (event: any) => {
const locationFilter: string = event.target.value;
const capacityFilter: any = event.target.value;
// console.log(typeof capacityFilter);
const filteredArr = tables.filter(
(table) => table.location === locationFilter
);
// console.log(filteredArr);
dispatch(filterTables(filteredArr));
};
const changeCapacity = (event: any) => {
// const locationFilter: string = event.target.value;
const capacityFilter: any = event.target.value;
// console.log(typeof capacityFilter);
const filteredArr = tables.filter(
(table) => table.capacity >= Number.parseInt(capacityFilter)
);
// console.log(filteredArr);
dispatch(filterTables(filteredArr));
};
const locations: string[] = tables.map((table) => table.location);
const capacity: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const uniqueLocations = [...new Set(locations)];
return (
<Form>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Select a table</Form.Label>
<Form.Control
onChange={handleChange}
as="select"
aria-label="form-select-location"
>
<option>Select your table's location</option>
{uniqueLocations &&
uniqueLocations.map((location: string, index: number) => (
<option aria-label="location" key={index} value={location}>
{location}
</option>
))}
</Form.Control>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Select the capacity of your table</Form.Label>
<Form.Control
onChange={changeCapacity}
as="select"
aria-label="form-select-capacity"
>
<option>Number of persons sitting </option>
{capacity &&
capacity.map((capacity: number, index: number) => (
<option aria-label="capacity" key={index} value={capacity}>
{capacity}
</option>
))}
</Form.Control>
</Form.Group>
</Form>
);
};
export default TableFilter;
here's my GitHub repo:
https://github.com/miki-miko/booking-system-testing
You could take an array of all filters and filter the data by all filters, you actually have.
For example take two filters, like
capacity = n => ({ capacity }) => capacity >= n;
place = p => ({ place }) => place === p;
Then put them ino an array. It does not matter, if you use only one or more, if necessary.
filters = [
capacity(2),
place('Inside')
]
As result, it returns objects where all filters return true, or at least a truthy return value.
This one with Array#every returns the objects where all conditions are true.
result = data.filter(o => filters.every(fn => fn(o))); // all
If only one constraint has to be true, take Array#some instead.
result = data.filter(o => filters.some(fn => fn(o))); // exists
You should filter the data in your selector since it is derived data and you should not store such data in your redux state. Here is a simple example of how you could do this:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const initialState = {
tables: ['A', 'B', 'C', 'D', 'E'].flatMap((place) =>
[...new Array(5)].map((_, index) => ({
place,
capacity: index + 1,
}))
), //not sure if filter needs to be here, is is shared
// by multiple components in your application?
filter: {
capacity: 'all',
place: 'all',
},
};
//action types
const SET_FILTER = 'SET_FILTER';
//action creators
const setFilter = (key, value) => ({
type: SET_FILTER,
payload: { key, value },
});
const reducer = (state, { type, payload }) => {
if (type === SET_FILTER) {
const { key, value } = payload;
return {
...state,
filter: {
...state.filter,
[key]: value,
},
};
}
return state;
};
//selectors
const selectTables = (state) => state.tables;
const selectFilter = (state) => state.filter;
const selectPlaces = createSelector(
[selectTables],
(tables) => [...new Set(tables.map(({ place }) => place))]
);
const selectCapacities = createSelector(
[selectTables],
(tables) => [
...new Set(tables.map(({ capacity }) => capacity)),
]
);
//return true if value is all, this is specific to the filer value
// if the value is "all" then return true
//When passing a function to this it returns a function that takes a value
// when calling that function with a value it returns a function that
// takes an item
const notIfAll = (fn) => (value) => (item) =>
value === 'all' ? true : fn(value, item);
//specific filter
const capacityBiggerThan = notIfAll(
//could do more abstraction here with getProp, and re usable compare
// functions but leave this out for simplicity
(value, item) => item.capacity >= Number(value)
);
const isPlace = notIfAll(
(value, item) => value === item.place
);
//Apply multiple filter functions, it receives an array of filter functions
// and returns a function that receives an item as parameter, when the item
// is passed to this function it will call all filter functions passing this item
const mergeFilters = (filterFunctions) => (item) =>
filterFunctions.every((filterFunction) =>
filterFunction(item)
);
//select filtered data
const selectFilterData = createSelector(
[selectTables, selectFilter],
(tables, { place, capacity }) => {
return tables.filter(
mergeFilters([
isPlace(place),
capacityBiggerThan(capacity),
])
);
}
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
() => (next) => (action) => next(action)
)
)
);
const FilterSelect = React.memo(function FilterSelect({
filterKey,
value,
values,
}) {
const dispatch = useDispatch();
return (
<select
value={value}
onChange={(e) =>
dispatch(setFilter(filterKey, e.target.value))
}
>
<option value="all">all</option>
{values.map((place) => (
<option key={place} value={place}>
{place}
</option>
))}
</select>
);
});
const App = () => {
const { capacity, place } = useSelector(selectFilter);
const places = useSelector(selectPlaces);
const capacities = useSelector(selectCapacities);
const filterData = useSelector(selectFilterData);
return (
<div>
<FilterSelect
filterKey="place"
value={place}
values={places}
/>
<FilterSelect
filterKey="capacity"
value={capacity}
values={capacities}
/>
<div>
<h1>result</h1>
<pre>
{JSON.stringify(filterData, undefined, 2)}
</pre>
</div>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
The code may be confusing since there are functions that get a function passed to it and return a function that when called with a value will return yet another function. You could rewrite the filter in the following way but it would not be re usable and more difficult to extend:
const selectFilterData = createSelector(
[selectTables, selectFilter],
(tables, { place, capacity }) => {
//just put all logic in one function, easier to read but repeating logic
// and more difficult to maintain if many values are used or rules change
return tables.filter((item) => {
const placeFilter =
place === 'all' ? true : item.place === place;
const capacityFilter =
capacity === 'all'
? true
: item.capacity >= Number(capacity);
return placeFilter && capacityFilter;
});
}
);

react how to not be so repetitive using useState hooks

I am making a form that uses many fields to post the data into a database.
I have over 80 fields like "title, body HTML, price, compare price, vendor, weights", etc.
and my code is very repetitive, is there a way to make my code shorter? I shaved a lot of my code because it's over 600 lines of code and would be too confusing to post the whole thing
I made 2 separate functions handleChange and selectHandler as little helpers to get the value of the dropdowns datalist inputs to be stored into state... the values have to be stored in separate states as I need each one to do an axios call to store its specific fields into the right data field.
import React, { useState } from "react";
function handleChange(e, setter) {
return setter({ value: e.target.value });
}
function selectHandler(setter) {
return (
<>
<input
list="headers"
placeholder="Select one"
onChange={(e) => handleChange(e, setter)}
/>
{/* headers comes from mapped api in another file */}
<datalist id="headers">{headers}</datalist>
</>
);
}
function PushToDB() {
const [showExtraImageInputs, setShowExtraImageInputs] = useState(false);
const [titleHeader, setTitleHeader] = useState();
const [handleHeader, setHandleHeader] = useState();
const [descriptionHtmlHeader, setDescriptionHtmlHeader] = useState();
const [image1Header, setImage1Header] = useState();
const [image2Header, setImage2Header] = useState();
const [altText1, setAltText1] = useState();
const [altText2, setAltText2] = useState();
return (
<>
<form onSubmit={(e) => e.preventDefault()}>
// each label uses the helpers to get the dropdown values and store it in state
<label>Title: {selectHandler(setTitleHeader)}</label>
<label>Body html: {selectHandler(setDescriptionHtmlHeader)}</label>
<label>Handle: {selectHandler(setHandleHeader)}</label>
<label>Image: {selectHandler(setImage1Header)}</label>
<label>Img alt text: {selectHandler(setAltText1)}</label>
{/* ADD MORE IMAGES */}
{showExtraImageInputs && (
<>
<div>Image 2: {selectHandler(setImage2Header)}</div>
<div>Img alt text 2: {selectHandler(setAltText2)}</div>
</>
)}
</form>
</>
);
}
export default PushToDB;
this is how the axios data looks like. as you can see I need each value from state. and again, its over 80 fields.
useEffect(() => {
if (pushState && apiData) {
let productValues = apiData.data.data;
productValues.map((e) => {
let url = `url`;
return axios
.get(url)
.then((res) => {
if (res) {
// if the data is already in db, do not push
if (res.data.products.length === 0)
// if there is no data then push data
return setProductData({
variables: {
// values from state
title: e[titleHeader?.value],
descriptionHtml: e[descriptionHtmlHeader?.value],
handle: e[handleHeader?.value],
img1: e[image1Header?.value] ?? "",
alt1: e[altText1?.value],
img2 : e[image2Header?.value] ?? '',
alt2: e[altText2?.value],
img3: e[image3Header?.value] ?? '',
// and so on
},
});
}
// this is the logger of whats being pushed into the database
})
.then((res) => {
if (res)
return axios.post("http://localhost:4000/api/v1/Extradb", {
data: {
title: res?.data?.productCreate?.product?.title,
handle: res?.data?.productCreate?.product?.handle,
item_id: res?.data?.productCreate?.product?.id,
},
});
});
});
}
}, []);
came out with a solution... I just needed to make an object
function App() {
const [userInputs, setUserInputs] = useState({})
function handleChange(e) {
const { value, name } = e.target
setUserInputs(prevState => ({
...prevState,
[name]: value
}))
}
function handleInputNaming(name) {
let capitilizedWord = name.charAt(0).toUpperCase() + name.slice(1);
return (<input placeholder={capitilizedWord} name={name} value={userInputs[name]} onChange={handleChange} />)
}
return (
<div className="App">
{handleInputNaming('title')}
{handleInputNaming('handle')}
{handleInputNaming('image')}
</div>
);
}
export default App;

One dropdown menu affects another dropdown menu

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

Categories

Resources