Dropdown menu in React, filter through list - reactjs

I am building a rest countries app in react and I'm trying to filter by region through a list. I would like to use a dropdown menu. I have tried using react select, creating a new component, this is my code
const DropdownMenu = (props) => {
const options = [
'', 'Africa', 'America', 'Asia', 'Europe', 'Oceania'
];
const defaultOption = options[0];
const handleFilterInput = (event) => {
let value = event.target.options;
props.handleRegionSearch(value);
};
return(
<div>
<Select options={options} onChange={handleFilterInput} value={defaultOption} placeholder="Select a region"/>
</div>
)
}
export default DropdownMenu;
My problem is, I can not get the value to change to the option selected, therefore it's impossible for me to filter the list I took from the api.
Does anyone have a possible solution for this?
Thanks

You are setting the value wrong
value={defaultOption}
This default option is never changing.
What you can do is catch the current value from onChange() and set the value somewhere (preferably in state) and then use that stored value as the value of dropdown component
Something like this.
const options = [
'', 'Africa', 'America', 'Asia', 'Europe', 'Oceania'
];
const [selectedValue , setSelectedValue ] = useState(options[0]);
const handleFilterInput = (event) => {
let value = event.target.options;
setSelectedValue(value);
props.handleRegionSearch(value);
};
return(
<div>
<Select options={options} onChange={handleFilterInput} value={selectedValue} placeholder="Select a region"/>
</div>
)

Thanks! In the end, I used a react-select component which did solve a this issue on it's own.

Related

React-Select: Page turns white after selecting an option

I am pretty new to React and am trying to use React-Select for a simple dropdown menu.
When you selected an option it should display the value under it, for that I'm using the onChange function and the useState Hook, but everytime I select something, the whole page just turns white.
App.js
import "./App.css";
import Select from "react-select";
import { useState } from "react";
function App() {
const [selected, setSelected] = useState(0);
const options = [
{ value: "1", label: "a" },
{ value: "2", label: "b" },
{ value: "3", label: "c" },
];
return (
<div>
<Select placeholder="Choose one"
defaultValue={selected}
onChange={setSelected}
options={options}
/>
<h1>{selected}</h1>
</div>
);
}
export default App;
Any help is appreciated, thank you.
I've done a simple edit on your code:
When onChange fires you set the selected value with onChange={(e)=>setSelected(e.value)}
Here's a functional codesandbox.
If you inspect your console in the browser, you'll see the issue is coming from the h1 component. This is probably what you're trying to achieve:
<h1>{selected.value}</h1>
I found a way to solve this, however I don't know if this is the best solution.
I created a function handleChange
const handleChange = e => {
setSelected(e.value);
}
and added a value prop, and called the function with onChange
<Select placeholder="Choose one"
defaultValue={selected}
value={options.find(obj => obj.value === selected)}
onChange={handleChange}
options={options}
/>
I don't know why
<h1>{selected.value}<h1>
doesn't work, since it's practically the same what I'm doing in handleChange, right?

Reloading a button on event change in React Typescript

I am having a simple form in React, which looks like:
const [placeOptions] = useState([
{ value: 'USA', label: 'USA' },
{ value: 'MEX', label: 'Mexico' },
]);
const [name, setName] = useState('');
const [place, setPlace] = useState('USA');
....
<input onChange={event => setName(event.target.value)} type="text"/>
<select onChange={event => setPlace(event.target.value)}>
{placeOptions.map(item => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
<CustomButton id="custom-btn" props={[name, place]} />
The above Custom button is just rendering once and is taking the default null and 'USA' value. It should Ideally send props to every event change, possibly refreshing the component once event is triggered. I am unable to determine how do I refresh a component on event change and pass the correct state to the props.
Edit: The below is the CustomButton.tsx file:
export function CustomButton({ props, id }: { props?:any, id?:string}) {
var name = props ? props[0] : '';
var place = props ? props[1] : '';
useEffect(() => {
renderButton(id);
}
return(
<React.Fragment>
<div id={id}></div>
</React.Fragment>
);
async function renderButton(id: string) {
... // Some logic involving the props passed
}
}
Edit 2:
This the code sandbox: https://codesandbox.io/s/amazing-dust-315dk?file=/src/App.js
All I want is to change the props too and dynamically render the custom button.
The problem is how you define the name and the place variable in CustomButton Component.
Variables of javascript defined like var let, and const will not trigger re-renders in React Button. States and Props only can trigger re-renders in React Components.
So if you do something like this in the parent file:
// All same code excepet
<CutomButtom id="cutom-btn" name={name} place={place} />
You can get name and place directly from props and use them as it is like:
export function CustomButton({ name, place, id }: { name: string, place: string, id:string}){
// NO need for defining name and place now, just use them directly...
}
Another improvement you can make is to define PropsType separately:
export interface CustomButtonProps {
id: string;
name:string;
place:string;
}
export function CustomButton({name, place, id}:CustomButtonProps){
}

react-select property name for value

I have this code:
import React from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
const optionsExample = [
{
code: "CLP",
country: "CLP",
minimumAmount: 10000
},
{
code: "USD",
country: "US",
minimumAmount: 25000
}
];
const handleChange = (newValue, actionMeta) => {
console.log("change newValue", newValue); // object changes but it's not selected the right one
};
const CustomControl = () => (
<Select
defaultValue={optionsExample[0]}
formatOptionLabel={({ code }) => code}
options={optionsExample}
onChange={handleChange}
/>
);
ReactDOM.render(<CustomControl />, document.getElementById("root"));
It doesn't work ok because react-select expect that the value of each object on the data array be named value. How can I pass data like in the example? I need that data has those properties, I can't change country to value.
Actually, I used the props formatOptionLabel to change the default label field from the object to code but I don't know how to do the same but for the value.
Demo with my code, with the problem:
https://codesandbox.io/s/react-select-formatoptionlabel-forked-deuw1?file=/index.js:0-756
This is working because each object has it's value field and that's what is expected by react-select
https://codesandbox.io/s/react-select-formatoptionlabel-forked-d9bdj
If you can't move the mountain, then walk around it.
use inline .map to convert your array of objects
const CustomControl = () => (
<Select
defaultValue={optionsExample[0]}
formatOptionLabel={({ code }) => code}
options={optionsExample.map(x => ({ value: x.country, label: x.code })}
onChange={handleChange}
/>
);
or better still use useMemo to create a new array, if your array changes
const selectOptions = useMemo(() => optionsExample.map(x => ({ value: x.country, label: x.code }),[optionsExample])
<Select
defaultValue={selectOptions[0]}
formatOptionLabel={({ code }) => code}
options={selectOptions}
onChange={handleChange}
/>

How can i define a default value for react-select v1

I had a react-select rendering a list of emails, and i need to keep the selected emails as a default option when the email is selected and saved, but the defaultValues are not working. How can i do that?
Here is my select component:
const [selectedOption, setSelectedOption] = useState("")
const makeEmailOption = item => ({
value: item.id,
label: item.ccEmail,
id: item.id,
chipLabel: item.ccEmail,
rest: item,
selected: item.selected
})
const makeEmailOptions = items => items.map(makeEmailOption)
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
onChange={handleChange}
value={selectedOption}
/>
</div>
)
I receive everything as props and work with that to make the options. How can i do that to make the default value if a field selected is true?
You almost have it, but in this case, you are setting the value to the selectedOption instead of setting the defaultValue. Also, you are changing the default value each time there is a change, which shouldn't be needed.
const defaultVal = {value: selectedOption, label: selectedOption};
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
defaultValue={defaultVal}
/>
</div>
)
I came with the following solution, since my component use a function to set some variables to the select, i use a useEffect to call that with a filter right after the page render.
useEffect(() => {
handleChange(makeEmailOption(props.ccemailfilter.filter(x => x.selected)))
}, [])
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
So, the handleChange are called on the onChange of the select and once after the page loads, to create a value to the select to use.

How to simulate selecting from dropdown in Jest / enzyme testing?

I'm trying to write jest tests for my React component that has a dropdown like this:
<select id="dropdown" onChange={this.handlechange} ref={this.refDropdown}>
{this.props.items.map(item => {
return (
<option key={item.id} value={item.id}>
{item.name}
</option>
);
})}
</select>
and the handler looks like this:
handlechange = () => {
const sel = this.refDropdown.current;
const value = sel.options[sel.selectedIndex].value;
//...
}
I want to simulate a user selecting the 2nd entry (or anything other than the first) in the list but am having trouble. If I simulate a "change" event it does fire the call to handlechange() but selectedIndex is always set to zero.
I tried this code in the jest test but it doesn't cause selectedIndex to be accessible in the handler.
const component = mount(<MyComponent/>);
component.find("#dropdown").simulate("change", {
target: { value: "item1", selectedIndex: 1 }
});
What happens is almost correct. If I look at the incoming event, I can see e.value is set to "item1" as I set, but it doesn't act like the selection was actually made.
I've also tried trying to send "click" simulations to the Option element directly but that does nothing.
What's the right way to simulate a selection from a dropdown?
Try this approach:
wrapper.find('option').at(0).instance().selected = false;
wrapper.find('option').at(1).instance().selected = true;
You can trigger a change event since you have your this.handlechange trigger onChange:
const component = mount(<MyComponent/>);
component.find('#dropdown').at(0).simulate('change', {
target: { value: 'item1', name: 'item1' }
});
I would say you have to add .at(0) because Enzyme will find a list of values even if you only have one element with that ID.
Try changing the html "onInput" to "onChange" because you are simulating the "change" event in jest.
Short Answer -
Use the following snippet
import userEvent from "#testing-library/user-event";
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), ["option1"]);
Detailed Answer -
.tsx file
.
.
<Form.Select
aria-label="Select a value from the select dropdown"
required
onChange={(e) => {
console.log("Option selected from the dropdown list", e.target.value);
optionChangedHandler(e.target.value);
}}
data-testid="select-element-test-id"
>
...CODE FOR RENDERING THE LIST OF OPTIONS...
</Form.Select>
.
.
.test.tsx file
import userEvent from "#testing-library/user-event";
it("Check entire flow", async () => {
render(
<YourComponent/>
);
// CHECK IF SELECT DROPDOWN EXISTS
const selectDropdown = await waitFor(
() => screen.getByTestId("select-element-test-id"),
{
timeout: 3000,
}
);
expect(selectDropdown ).toBeInTheDocument();
//"option2" is the element in the select dropdown list
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), [
"option2",
]);
}
The above code will trigger the onChange function of the select element.

Resources