Dynamically updating dropdown menu in React - reactjs

Using fetch, I want to dynamically populate the City material-ui dropdwon (Select) when I select a value from the State dropdown, but could not do so. When I do the same without using the fetch, it works fine. I think the problem is with the promise being returned by the fetch call. There is no problem in the fetch call as I can see the list of cities in return. Please suggest how to do it.
import React from 'react';
import { createStyles, makeStyles, Theme } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import FormHelperText from '#material-ui/core/FormHelperText';
import FormControl from '#material-ui/core/FormControl';
import {Select, MenuItem} from '#material-ui/core';
import './App.css';
export function getStates() {
return [
{name: 'California', id: "1"},
{name: 'New York', id: "2"},
]
}
function Home() {
const useStyles = makeStyles((theme: Theme) =>
createStyles({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}),
);
const [State, setState] = React.useState([]);
const [cities, setCities] = React.useState([]);
const selectStyle = makeStyles(theme => ({
root: {
textDecoration: 'none',
color: 'red',
alignItems: 'center',
fontWeight: "bold",
display: "flex",
justifyContent: "space-around",
fontSize: 18,
margin: 0,
'&:hover': {
textDecoration: 'none'
}
},
}));
function getCities() {
var s = '' // JSON body goes here
const fetchData = async () => {
const cities = [];
try {
const res = await fetch('http://127.0.0.1:8080',
{
method : "POST",
headers: {"content-type": "text/plain"},
body: s
}
);
const data = await res.json();
console.log("state response status: " + res.status)
for(var key in data.cities) {
cities.push({id: key, name: data.cities[key]})
}
return cities;
}
catch (err) {
console.log("Fetch Exception: " + err)
}
}
const cities = fetchData();
return cities;
}
const handleStateChange = (event: React.ChangeEvent< { value: unknown} >) => {
setState(event.target.value);
const r = getCities();
setCities([r]);
}
const fixed_states = getStates();
const classes = useStyles()
const selectClass = selectStyle()
return (
<div className="main-select">
<container>
<FormControl required className={classes.formControl}>
<InputLabel id="sel">State</InputLabel>
<Select labelId="state_select_labelid" id="state_select_id" name="state_select_name" onChange={handleStateChange} className={selectClass.root}>
{fixed_states.map(({id, name}, index) => (
< MenuItem key={id} value={name}>
{name}
</MenuItem>
)) }
</Select>
<FormHelperText></FormHelperText>
</FormControl>
<FormControl required className={classes.formControl}>
<InputLabel id="city_input_label_id">City</InputLabel>
<Select labelId="city_select_labelid" id="city_select_id" name="city_select_name">
{cities.map(({id, name}, index) => (
< MenuItem key={id} value={name}>
{name}
</MenuItem>
))}
</Select>
<FormHelperText></FormHelperText>
</FormControl>
</container>
</div>
);
}
export default Home;

You code:
const handleStateChange = (event: React.ChangeEvent< { value: unknown} >) => {
setState(event.target.value);
const r = getCities();
setCities([r]);
}
but getCities return array of cities and then you set array cities in state like array of array.
So just update argument in setCities row to
const handleStateChange = (event: React.ChangeEvent< { value: unknown} >) => {
setState(event.target.value);
const r = getCities();
setCities(r);
}

Related

react-select, AsyncSelect only able to select one option even i added isMulti after that it display no options

I can select first option successfully, but after that it doesn't display any option, can't add second option and I even added isMulti.
import React from "react";
import AsyncSelect from "react-select/async";
import makeAnimated from "react-select/animated";
import { options } from "../colorOptions";
import chroma from "chroma-js";
const animatedComponents = makeAnimated();
export const SelectBox = () => {
const loadOptions = (searchValue, callback) => {
console.log(searchValue);
setTimeout(() => {
const filteredOptions = options.filter((option) =>
option.name.toLowerCase().includes(searchValue.toLowerCase())
);
console.log(filteredOptions);
callback(filteredOptions);
}, 1000);
};
const colorStyles = {
control: (styles) => ({ ...styles, backgroundColor: "white" }),
option: (styles, { data, isDesable, isFocused, isSelected }) => {
return { ...styles, color: data.colorCode };
},
multiValue: (styles, { data }) => {
const color = chroma(data.colorCode);
return {
...styles,
backgroundColor: color.alpha(0.1).css(),
color: data.colorCode
};
},
multiValueLabel: (styles, { data }) => ({
...styles,
color: data.colorCode
})
};
return (
<AsyncSelect
key={options.length}
loadOptions={loadOptions}
option={options}
closeMenuOnSelect={false}
components={animatedComponents}
isMulti
defaultOptions
styles={colorStyles}
/>
);
};
code sandbox link:
https://codesandbox.io/s/dreamy-water-j2m55v?file=/src/components/SelectBox.jsx:0-1401
code sandbox link:
https://codesandbox.io/s/dreamy-water-j2m55v?file=/src/components/SelectBox.jsx:0-1401
my mistake
i should provide my collection of option in this format
export const options = [ { id: 1, value: "Red", colorCode: "#FF0000", label: "Red" }, ];
when i change to this format the code works

Why I can't update state with Context API

I Make Kakaotalk with ReactJS
I make Search User Page now, and I use Context API because use Global State
addFriend/Index.tsx
import Header from "components/friends/Header";
import InputUserId from "./InputUserId";
import Profile from "./Profile";
import { AddFriendProvider } from "./AddFriendContext";
const AddFriend = () => {
<>
<AddFriendProvider>
<Header />
<InputUserId />
<Profile />
</AddFriendProvider>
</>;
};
export default AddFriend;
addFriend/AddFriendContext.tsx
import React, { createContext, useState } from "react";
const AddFriendContext = createContext<any>({
state: {
userId: "",
searchResult: "",
},
actions: {
setUserId: () => {},
setSearchResult: () => {},
},
});
const AddFriendProvider = ({ children }: any) => {
const [userId, setUserId] = useState("");
const [searchResult, setSearchResult] = useState("");
const value = {
state: { userId, searchResult },
actions: { setUserId, setSearchResult },
};
console.log(value);
return (
<AddFriendContext.Provider value={value}>
{console.log(setUserId, setSearchResult)}
{children}
</AddFriendContext.Provider>
);
};
export { AddFriendProvider };
export default AddFriendContext;
addFriend/InputUserId.tsx
import styled from "styled-components";
import Input from "components/common/Input";
import { ChangeEvent, useContext } from "react";
import AddFriendContext from "./AddFriendContext";
const StyledInputWrapper = styled.div`
width: 370px;
height: 80px;
display: flex;
align-items: center;
`;
const InputUserId = () => {
const { state, actions } = useContext(AddFriendContext);
console.log(state, actions);
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
actions.setUserId(value);
};
const inputStyle = {
width: "100%",
height: "40px",
paddingLeft: "10px",
fontWeight: "bold",
};
const subInputStyle = {
borderBottom: "2px solid lightgray",
focus: "2px solid black",
};
return (
<StyledInputWrapper>
<Input
type="text"
placeholder="카카오톡 아이디를 입력해주세요."
required
inputStyle={inputStyle}
subInputStyle={subInputStyle}
value={state.userId}
onChange={onChange}
/>
</StyledInputWrapper>
);
};
export default InputUserId;
If i change input element in InputUserId.tsx call actions.setUserId(value) but It not worked.
I think login is >
When Change Input, call actions.setUserId and update state.userId
Change Input value when state.userId Update
But it now Worked..
Help me and if some trouble in my code feedback me plz. thanks.

Trying to generate alerts for invalid fetches for cities with OpenWeatherMaps API

So I'm trying to generate alerts for users that attempt to access weather data for invalid cities via the OpenWeatherMaps API.
I'm mostly there. However, when I test this out, two things happen:
Browser generates the proper alert.
However, once you click on the confirmation button for the alert...
"Unhandled Rejection (TypeError): Cannot destructure property 'data' of '(intermediate value)' as it is undefined."
Here is the code that I'm currently using:
const fetchWeather = async (query) => {
const { data } = await axios.get(
URL,
{
params: {
q: query,
units: "metric",
APPID: API_KEY,
},
}).catch (function (error) {
alert(error.response.data.message)
})
};
Full code:
Search.jsx component:
import React, { useState } from "react";
import fetchWeather from "../api/fetchWeather";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import InputAdornment from '#material-ui/core/InputAdornment';
import SearchIcon from '#material-ui/icons/Search';
const Search = () => {
let [displayResults, setDisplayResults] = useState(false);
let [query, setQuery] = useState("");
let [feelsLike, setFeelsLike] = useState(0);
let [mainTemp, setMainTemp] = useState(0);
let [description, setDescription] = useState("");
let [main, setMain] = useState("");
let [iconID, setIconID] = useState("");
let [windSpeed, setWindSpeed] = useState("");
let [windGust, setWindGust] = useState("");
let [windDirection, setWindDirection] = useState("");
let [name, setName] = useState("");
let [country, setCountry] = useState("");
const useStyles = makeStyles((theme) => ({
button: {
backgroundColor: "#FDB124",
color: "black",
fontFamily: "Mortal Kombat",
"&:hover": {
background: "#B57602",
},
},
root: {
"& > *": {
display: 'flex',
margin: theme.spacing(1),
// width: "25ch",
},
input: {
color: "white",
},
primary: {
backgroundColor: "#FDB124",
},
"& label.Mui-focused": {
color: "#FDB124",
},
"& label": {
color: "#FDB124",
fontFamily: "Mortal Kombat",
},
"& input": {
color: "#FDB124",
fontFamily: "Mortal Kombat",
},
"& .MuiInput-underline:after": {
borderBottomColor: "#FDB124",
},
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "#FDB124",
},
"&:hover fieldset": {
borderColor: "#FDB124",
},
"&.Mui-focused fieldset": {
borderColor: "#FDB124",
},
},
},
}));
const weatherSearch = async (e) => {
if (e.key === "Enter") {
const data = await fetchWeather(query);
setDisplayResults(true);
setFeelsLike(data.main.feels_like);
setMainTemp(data.main.temp);
setDescription(data.weather[0].description);
setMain(data.weather[0].main);
setIconID(data.weather[0].icon);
setWindSpeed(data.wind.speed);
setWindGust(data.wind.gust);
setWindDirection(data.wind.deg);
setName(data.name);
setCountry(data.sys.country);
setQuery("");
}
};
const classes = useStyles();
return (
<div>
<h1 className="cityChoose">CHOOSE YOUR CITY:</h1>
<TextField
id="outlined-basic"
label="Enter City"
variant="outlined"
color="secondary"
size="small"
spellCheck="false"
className={classes.root}
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={weatherSearch}
InputProps={{
startAdornment: (
<InputAdornment position="start" style={{color: "#FDB124"}}>
<SearchIcon />
</InputAdornment>
),
}}
/>
{displayResults ? null : <h4>Example: Chicago, IL, US</h4>}
{displayResults ? (
<>
<h1>The current weather in {name}, {country} is:</h1>
<span>
<div>
{description}
<br />
<img
src={"http://openweathermap.org/img/wn/" + iconID + "#2x.png"}
/>
</div>
<h2>Temperature:</h2>
<br />
<div>
{(mainTemp * 1.8 + 32).toFixed(1)} °F / {mainTemp.toFixed(1)}{" "}
°C
</div>
<br />
<br />
<h2>Winds:</h2>
<div>Wind Direction: {windDirection}</div>
<div>Wind Speed: {windSpeed} MPH</div>
<div>Wind Gusts: {windGust} MPH</div>
</span>
</>
) : null}
</div>
);
};
export default Search;
fetchWeather.jsx:
import React from "react";
import axios from "axios";
const URL = "https://api.openweathermap.org/data/2.5/weather";
const API_KEY = "*key is here*";
const fetchWeather = async (query) => {
const { data } = await axios.get(
URL,
{
params: {
q: query,
units: "metric",
APPID: API_KEY,
},
}).catch (function (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log("Error: ", error.message);
}
});
console.log(data);
return data;
};
export default fetchWeather;
Ok, so the problem you're having has to do with the fact that axios throws an error and does not return an object with key data back to const { data } = await axios.get(. Fixing this is quite straightforward.
The reason it cannot destructure data from your bad axios call is because you're not accommodating it inside the catch block
.catch (function (error) {
alert(error.response.data.message)
})
Your catch block should instead look like this:
.catch (error => {
return {data: error.response.data ? error.response.data : 'No data'}
}
)
So the final (and working) version of your fetchWeather function is...
const fetchWeather = async (query) => {
const { data } = await axios.get(
'https://httpstat.us/400',
{
params: {
q: query,
units: "metric",
APPID: 'XXXXXXXX',
},
})
.catch (error => {
return {data: error.response.data ? error.response.data : 'No data'}
}
)
console.log(data)
};
You can try it with these two test URLs:
https://httpstat.us/200 returns 200 OK
https://httpstat.us/400 returns 400 Bad Request
The latter will trigger the catch.
Please keep in mind that my code, as written, will throw an error if you're calling a domain that does not resolve to an ip (e.g. http://hskjfhshgsg.com/api/sss/sss or something like that.)
Try to check for the error like this:
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log("Error", error.message);
}
Working sample: https://codesandbox.io/s/openweathermap-test-forked-35781?file=/src/index.js:434-749

material ui Select handleChange

const handleChangeMultiple = (event: React.ChangeEvent<{ value: unknown }>) => {
const { options } = event.target as HTMLSelectElement;
const value: string[] = [];
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
value.push(options[i].value);
}
}
setPersonName(value);
};
I just started using material UI and they have this great Select component that let you select from a list.
The code above is the sample code they provided that work for a string[], but my project is selecting from an object array.
example: {label: "string", value:"string", b: boolean}
My question is how can I modify this handleChange to work for an object array?
I try changing string[] to the dataType[] I created but I get the error "Argument of type 'string' is not assignable to parameter of type 'dataType'.
const handleChangeMultiple = (event: ChangeEvent<{ value: dataType[] }>) => {
console.log(event.target.value)
}
When I try this, it console log the correct value selected, but when I change console.log to setValue(event.target.value), I get error value.map is not a function.
{value.map((item) => (
option key={item.value} value={item.label}>
{item.label}
</option>
The code above work when console.log.
Select component is using basic type to determine which options are selected (Comparing objects is not so easy). You can use the array index:
import React from 'react';
import { createStyles, makeStyles, Theme } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
maxWidth: 300,
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: 2,
},
noLabel: {
marginTop: theme.spacing(3),
},
}),
);
interface User {
value: string,
label: string,
superUser: boolean
}
const users = [{
value: 'OliverHansen',
label: 'Oliver Hansen',
superUser: true
}, {
value: 'VanHenry',
label: 'Van Henry',
superUser: false
}, {
value: 'AprilTucker',
label: 'April Tucker',
superUser: true
}, {
value: 'RalphHubbard',
label: 'Ralph Hubbard',
superUser: false
}, {
value: 'OmarAlexander',
label: 'Omar Alexander',
superUser: true
}, {
value: 'CarlosAbbott',
label: 'Carlos Abbott',
superUser: false
}];
export default function MultipleSelect() {
const classes = useStyles();
const [selectedUsers, setSelectedUsers] = React.useState<User[]>([]);
const [selectedUserIndexes, setSelectedUserIndexes] = React.useState<number[]>([]);
const handleChangeMultiple = (event: React.ChangeEvent<{ value: unknown }>) => {
const { options } = event.target as HTMLSelectElement;
const selectedUsers: User[] = [];
const selectedUserIndexes: number[] = [];
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
let selectedUserIndex = parseInt(options[i].value, 10);
selectedUsers.push(users[selectedUserIndex]);
selectedUserIndexes.push(selectedUserIndex);
}
}
console.log(selectedUserIndexes, selectedUsers);
setSelectedUserIndexes(selectedUserIndexes);
setSelectedUsers(selectedUsers);
};
return (
<div>
<FormControl className={classes.formControl}>
<InputLabel shrink htmlFor="select-multiple-native">
Native
</InputLabel>
<Select
multiple
native
value={selectedUserIndexes}
onChange={(e) => handleChangeMultiple(e)}
inputProps={{
id: 'select-multiple-native',
}}
>
{users.map((user, index) => (
<option key={index} value={index}>
{user.label} {}
</option>
))}
</Select>
</FormControl>
</div>
);
}

What is the best way to merge styles when wrapping Fabric components?

Consider I am wrapping a Fabric component where I apply some styles and want to merge any passed in styles from its props.
The best I could come up with is:
const { TextField, Fabric , IButtonProps, mergeStyleSets } = window.Fabric;
const MyTextField = (props: IButtonProps) => {
const { styles, ...otherProps } = props;
const myStyles = stylesProps => {
// props.styles can be a function, an object or undefined
const stylesAsObject = typeof (styles) === "function" ? styles(stylesProps) : styles;
return mergeStyleSets({ root: { maxWidth: 250 }, field: { backgroundColor: "pink"}}, stylesAsObject);
};
return <TextField styles={myStyles} {...otherProps} />;
}
const TextFieldExample () => (<MyTextField readOnly value="My text field" styles={{field: { fontWeight: 600}}} />
);
ReactDOM.render(<Fabric><TextFieldExample /></Fabric>, document.getElementById('content'));
This works but is a bit verbose.
Is there some version of mergeStylesets where I could instead write:
const myStyles = mergeStylesets({ root: { maxWidth: 250 }, field: { backgroundColor: "pink"}}, props.styles);
I discovered concatStyleSetsWithProps :
const { TextField, Fabric , IButtonProps, concatStyleSetsWithProps } = window.Fabric;
const MyTextField = (props: IButtonProps) => {
return <TextField {...props} styles={stylesProps => concatStyleSetsWithProps(props, { root: { maxWidth: 250 }, field: { backgroundColor: "pink"}}, props.styles)} />;
}
const TextFieldExample= () => (<MyTextField readOnly value="My text field" styles={{field: { fontWeight: 600}}} />);
const ExampleWrapper = () => <Fabric><TextFieldExample /></Fabric>;
ReactDOM.render(<ExampleWrapper />, document.getElementById('content'))
https://codepen.io/onlyann/pen/yLNXvPd?editors=1111

Resources