Add a search input inside a select without using a library - reactjs

I feel like there is a simple solution to this but I've been stuck on it for a while.
This is what I did:
export default function App() {
const [searchTerm, setSearchTerm] = useState('');
return (
<div>
<div>
<input type="text" onChange={(e) => setSearchTerm(e.target.value)} />
</div>
<select>
<option defaultOption>Choose option</option>
{SelectOptions.filter(({ val }) => val.includes(searchTerm)).map(
({ val, id }) => {
return (
<option key={id} value={id}>
{val}
</option>
);
}
)}
</select>
</div>
);
}
sandbox: https://stackblitz.com/edit/react-qx5fym?file=src%2FApp.js
As you can see I have an input above my select and it works. If I search something, the select option shorten the list. However, I'm struggling on trying to get the input inside the select.
I want to be able to get this:
Exclude the styling that isn't the goal right now.
Anyone know how to achieve this?

Related

Display items from array in select box react

I am working on an application where I am taking data(array of sectors) from firestore database and adding this array to select box options. My problem is that I can't properly display each item in a separate option, instead my entire array is displayed in one line in the select box option.
a screenshot of what my application looks like with the described problem.
Parts of the code responsible for getting an array from the database and displaying it in the select box options:
const [allSectors, setAllSectors] = useState([]);
useEffect(() => {
getSectors();
}, []);
const getSectors = async () => {
const data = await UserDataService.getAllSectorsData();
setAllSectors(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
const FormSelect = () => {
return allSectors.map((sector) => (
<option key={sector.id} value={sector.id}>
{sector.Manufacturing}
</option>
));
};
return (
<>
<Form onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="formUserSector">
<InputGroup>
<InputGroup.Text id="formUserSector">Sectors: </InputGroup.Text>
<Form.Select size="sm" onChange={(e) => setSectors(e.target.value)}>
{FormSelect()}
</Form.Select>
</InputGroup>
</Form.Group>
</Form>
</>
)
Not sure which UI library you are using. The first thing I noticed is that you use <option> tag without surrounding it with <select> tag
<option key={sector.id} value={sector.id}>
{sector.Manufacturing}
</option>
I'm assuming you are trying to implement HTML <select> dropdown. I think if you use only tags without surrounding it with it will just return strings, which is what happening in your example.
Try changing your FormSelect function to something like this:
const FormSelect = () => {
return (
<select>
{allSectors.map((sector) => (
<option key={sector.id} value={sector.id}>
{item}
</option>
))}
</select>
);
};
EDIT 1
From your comments, I noticed that your sector.Manufacturing is an array, but it should be a string. When you put an array inside option like this <option>{array}</option> your get all the array values squashed into one <option> tag.
try also looping over sector.Manufacturing rather than allSector. Hard to solve without seeing the full code, but it might look something like this:
const FormSelect = () => {
return (
<select>
{allSectors.map((sector) => (
<option key={sector.id} value={sector.id}>
{sector.Manufacturing.map((item) => {
return item;
})}
</option>
))}
</select>
);
};
EDIT 2
Try this:
const FormSelect = () => {
return (
<select>
{allSectors.map((sector) =>
sector.Manufacturing.map((item, i) => {
return (
<option key={i} value={item}>
{item}
</option>
);
})
)}
</select>
);
};

how to pass array values to Formik select

I am using Formik for a bunch of admin console forms that I have in my application. So far I did not have this use case.
My Formik forms use one of 2 custom components, either a Myinputtext(input box) or a MySelect(drop down). I dont have a need for any other components so far. Here is how my Myselect component looks like.
const MySelect = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<select className={props.className} {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
Over in the form I am passing values to this component like this
<MySelect className="select-input" label="Losing Player" name="losingPlayer">
<option value="">Select Losing Player</option>
<option value="player1">{state.Player1Name} </option>
<option value="player2">{state.Player2Name} </option>
All of this works for a few forms I have built so far. In the fourth form now, data coming back from the back end is coming as an array and I am trying to pass the array as input to the myselect component
<MySelect className="select-input" label="Losing Player" name="losingPlayer">
<option value="">Select Losing Player</option>
<option value="player1">{name of array object} </option>
This is failing and not providing the right result.
In the formik official docs it says there is a way to handle array objects like this
<Form>
<Field name="friends[0]" />
<Field name="friends[1]" />
<button type="submit">Submit</button>
</Form>
</Formik>
But my array size can be dynamic and I cannot hardcode, 0,1 like above.
I tried rendering the array inside the select component like this,
<MySelect className="select-input" label="Winning Player" name="winningPlayer">
{props.initialValues.map((player) => {
<option key={player} value={player}> {player} </option> })} </MySelect>
this does not throw any errors. but the drop down is displayed empty.
I am basically hoping to have the names in the array displayed as the dropdown. What is the right solution to tackle this?
This finally worked:-
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
{!props.player ? <select className={props.className} {...field} {...props} />
:
<select className={props.className}>
{props.player.map((player) => {
return (
<option key={player} value={player}>
{player}
</option>
)
})}
</select>
}
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
You need to map your array and render options inside your select like this:
{options?.map(({ value }) => (
<option key={value} value={value}>
{value}
</option>
))}

Populating option in react component with key value pair

I am trying to populate the option with authors name dynamically by providing a key value pair and want the result as shown but the result is not like but instead it displays four different forms for each author.
Please refer to images for a clear view of my question.
This is what I got:
import { gql ,useQuery} from '#apollo/client';
const getAuthorsQuery = gql`
{
authors{
name
id
}
}
`;
function AddBook() {
const { loading, error, data } = useQuery(getAuthorsQuery);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
console.log(data)
return data.authors.map(author => {
return (
<form id="add-book">
<div className="field">
<label>Book name:</label>
<input type="text" />
</div>
<div className="field">
<label>Genre:</label>
<input type="text" />
</div>
<div className="field">
<label >Author:</label>
<select >
<option key={author.id} value ={author.id}>Select Authors</option>
{author.name}
</select>
</div>
<button>+</button>
</form>
)
})
Instead of using map for entire form use at options alone.
{data.authors.map(author => <option key={author.id} value ={author.id}>Select Authors</option>)}
You have your order of operations wrong. You have placed the loop way too early in the flow of your code, and are therefore creating all of the JSX for the entire form for each option.
You can declare JSX as a variable earlier in the method and then use it in your final return JSX by wrapping it in {}
Also, you want to put the author.name inside of the option, and your Select Author option should have an empty value; like this:
let options = [];
for (let author in authors) {
options.push(<option key={author.id} value={author.id}>author.name</option>);
}
...
<select>
<option value="">Select Authors</option>
{options}
</select>

Firebase - set default value in select field after form submit

I have simple form with dropdown select menu. How do I reset the select field back to show first option after submission ( ---Select category---)? I have tried setCategory("") or setCategory("---Select category---") but it keeps showing category i have picked.
const [category, setCategory] = useState("");
function formSubmit(e) {
e.preventDefault();
firebase
.firestore()
.collection("posts")
.add({
category,
})
.then(() => {
setCategory("");
});
}
<form onSubmit={formSubmit}>
<div>
<label htmlFor="category">Post Category</label>
<select
name="category"
onChange={(e) => setCategory(e.currentTarget.value)}
>
<option value="">--- Select category ---</option>
<option value="react">React</option>
<option value="CSS">CSS</option>
<option value="misc">Misc</option>
</select>
<div>
<button type="submit">Add</button>
</div>
</div>
After doing a little bit of research I found this questions answered here, here, and here. However, I would advice you to use the react-advanced-form library which makes this task a lot easier and will give you more features to implement on your forms.
While using this library you want to use “form.reset()” on your action button. This will let you set initial values for each field and the action will set the values of your fields to this default value. Here is an example:
import React from 'react'
import { Form } from 'react-advanced-form'
import { Input } from 'react-advanced-form-addons'
export default class Example extends React.Component {
handleSubmitted = ({ res, fields, form }) => {
form.reset() // resets "username" field to "admin"
}
render() {
return (
<Form onSubmitted={this.handleSubmitted}>
<Input
name="username"
initialValue="admin" />
</Form>
)
}
}

Redux-form: Can't get children from Field component

I'm trying to render a select, and want to be able to do something like this to add options:
<Field component={RenderSelect} name="subjects" label="Subjects">
<option value="maths">Maths</option>
<option value="english">English</option>
</Field>
I have a createRenderer function:
const createRenderer = render => ({input, name, label, children}) => {
return (
<div key={name}>
<label htmlFor={name}>{label}</label>
{render(input, name, children)}
</div>
)
},
and my RenderSelect looks like this:
const RenderSelect = createRenderer((input, name, label, children) => {
return (
<select name={name} {...input}>
{children}
</select>
)
})
I was under the impression that I could just destructure the children prop off the Field like I do for input, name, label, etc, although this does not seem to work. When I run the code, no options appear in my select, and an inspection of the DOM verifies that there are no options. Any help would be much apprecited. Thanks in advance!
You just need to remove your label parameter
const RenderSelect = createRenderer((input, name, children) => {
return (
<select name={name} {...input}>
{children}
</select>
)
});

Resources