I'm new to React and I'm trying to select an option from the list and click the button to confirm the selection. I save the selected option in the "pickedUser" object. But when I change the state of the "pickedUser", I think the render is start again and the list is like at the beginning. I just want to select the option and click on the button without restarting select list. If you can help solve the problem and point out the mistakes I need to correct in order to get better. Thanks!
//pickedUser saving selected option from list.
constructor(props){
super(props);
this.state = {
users: [],
pickedUser:{
name:"",
email:"",
uloga:""
},
isLoading: true,
errors: null
};
this.handleChange = this.handleChange.bind(this);
this.routeChange = this.routeChange.bind(this);
}
//In handleChange I'm trying to set a value I get from select to pickedUser.
async handleChange(event) {
const eName = event.target.value.split(',')[0];
const eEmail = event.target.value.split(',')[1];
const eUloga = event.target.value.split(',')[2];
await this.setState({
pickedUser:{
name : eName,
email: eEmail,
role: eUloga
}
});
}
//And this is a part of my render.
render() {
const { isLoading, users, pickedUser } = this.state;
return (
<div>
<select
id="list"
value={pickedUser}
onChange={event => this.handleChange(event)}
>
{!isLoading ? (
users.map((user, key) => (
<option key={key}>
{user.name}, {user.email}, {user.role}
</option>
))
) : (
<p>Loading...</p>
)}
</select>
<button id="b1" type="button"
value={pickedUser}
onClick={event => {
this.handleSubmit(event);
this.routeChange();
}}>
Sign in
</button>
I wanted to function normally, that when I select the option, it stays selected, but it happens that when I select it, it is refreshed again.
I just have to tell that the value is good when the option is selected but the display is not.
Technically you only have to correct this line
<select
id="list"
value={pickedUser.name + "," + pickedUser.email + "," + pickedUser.role}
onChange={event => this.handleChange(event)}
>
value should not be object (pickedUser), but it should be string.
This is working example
But I can suggest following:
Make state.users object (not array). Email should be unique, so it can be used as key. For example:
this.state = { users: { "jack#mail.com": {name: "Jack", uloga: "aaa"},
"mark#mail.com": {name: "Mark", uloga: "aaa"} } }
In this case you'll be able to extract user from users by it email.
Object also support iteration like arrays useng Object.keys(users) or Object.values(users)
Use email as key for <option>. Index as keys is not good practice in React.
Add id to each <option> to easily identify selected option in event handler
Suggested version is here
Related
I have the following code:
import SearchBar from "material-ui-search-bar";
const data = [
{
name: "Jane"
},
{
name: "Mark"
},
{
name: "Jason"
}
];
export default function App() {
const [results, setResults] = useState();
const filteredResults = data.filter((item) => {
return Object.keys(item)?.some((key) => {
return item[key].includes(results?.toLowerCase());
});
});
return (
<div className="App">
<SearchBar
value={results}
onChange={(value) => setResults(value)}
placeholder="Please enter name..."
/>
{filteredResults.map((item) => {
return <li>{item.name}</li>;
})}
</div>
);
}
codesandbox
when I delete name from search bar using delete keyboard all names from data are displayed below the search bar, but if I click X button, it clears the search bar but doesn't display all names. Is there a way to add a function to this X button so when I click it, it acts the same way as delete keyboard?
You can pass a function to the onCancelSearch prop to reset the results state variable.
<SearchBar
value={results}
onChange={(value) => setResults(value)}
onCancelSearch={() => setResults('')}
/>
Suggestions
It's better to initialize results with an empty string. You can now remove the ? in results?.toLowerCase() since results will never be nullish (undefined or null).
const [results, setResults] = useState('')
You should pass the key prop to the li element. You can add an id property to the items array to use as the key or use the item index.
{
filteredResults.map((item) => (
<li key={item.id}>{item.name}</li>
))
}
And there are a couple of issues with the filtering logic.
You're converting the search query to lowercase but not the name. In your example, if you search for 'ja', nothing would show up even though matches exist (Jane and Jason).
filteredResults will throw an error if any of the object values do not have the includes method (You can reproduce the issue by adding a numeric id field to the array items). You could fix it by using a searchableKeys array to only perform the search in specific fields.
const searchableKeys = ['name']
const filteredResults = data.filter((item) =>
searchableKeys.some((key) =>
item[key].toLowerCase().includes(results.toLowerCase())
)
)
I would recommend renaming results to query or searchQuery for clarity.
Hello upon checking your problem, the reason why its remapping the list on delete key (in keyboard) because it triggers the onChange event of the searchBar to have the same event as the searchBar, i've tried it on my end and it seems that this solution can be solve your issue
<SearchBar
value={results}
onChange={(value) => setResults(value)}
placeholder="Please enter name..."
closeIcon={<button onClick={() => setResults("")}>clear</button>}
/>
the closeIcon props - overrides the close icon and its methods..
here is the documentation that i check material-ui-search-bar
here also the replicated/solved code-sandbox
Issue: Every time I refresh the page, the previously checked boxes get unchecked. I have 4 checkboxes. I checked two and then I refresh. The other two wont save. Im not sure what's wrong here. Could you please point my mistake. Much Appreciated.
const handleCheckboxChange = ([event]) => {
return event.target.checked;
};
const organizationInfo = {
// label name, variable name, value
"Organization Name": ["name", name],
"Organization Contact E-mail": ["email", email],
};
const renderNeedSection = () => {
if (organization) {
return (
<div>
{Object.entries(NEEDS).map(([key, label]) => (
<CheckBoxWrapper key={key}>
<Controller
as={Checkbox}
defaultChecked={needs[key]}
name={`needs.${key}`}
control={control}
onChange={handleCheckboxChange}
>
<Label inputColor="#000000">{label}</Label>
</Controller>
</CheckBoxWrapper>
))}
<span style={errorStyles}>
{errors.needs ? "Please select at least one option" : ""}
</span>
</div>
);
}
};
React state isn't persisted across reloads. If you want your data to persist you have among other options
Store it in a server and retrieve it when the page loads
Use the browser localStorage
My parent component looks like:
<div>
<Dropdown items={companiesData} handler={handleClick} />
....//More stuff
</div>
companiesData is an array of items with id, companyName etc.
I am creating my dropdown this way:
const Dropwdown = ({ items, handler }) => {
return (
<select onChange={handler}>
{items.map(({ id, value, companyName, companyType }) => (
<option
key={id}
value={value}
>
{`${companyName}, ${companyType} `}
</option>
))}
</select>
)
}
I know that from the handleClick function I can access e.target.value and get the value of the dropdown, but what if I want to get the whole object of that selected value (e.g. containing id, value, companyName etc.)and pass it back to the parent component?
in Dropdown, add value property to select and use the id like value={this.state.selectedValue}.
So you will have that value in ev.target.value.
Then, in your parent, you can do something like: companiesData.filter(company => company.id === ev.target.value). And you have the info there.
and of course set the selectedValue (using hooks or normal setState)
Another option (if you don't want to do filtering) is to simply send e.target to your handler instead of e.target.value.
In your handler, retrieve the info you need like this:
const parentHandler = target => {
const targetOptions = target.options;
const selectedValue = target.value;
console.log("selected value", selectedValue);
console.log("all html options array", targetOptions);
console.log("selected option html", targetOptions[target.selectedIndex]);
console.log(
"selected option name",
targetOptions[target.selectedIndex].getAttribute("name")
);
};
see a demo here
I'm getting a string with marked characters and replacing them with inputs to let users edit messages . I want to save it with new values and send it back to server. Briefly , Im spliting string by "/" , finding strings with "#" and replacing with inputs. Now I want add in a new array changed values and current indexes from input. But it saves only one value. May be could suggest me another way of doing it. This is challenging task for me .
my fiddle: https://jsfiddle.net/armakarma/d2qyha0w/11/
editModalText() {
let modalMessage="Hello, my name is /# Ann #/. I'm working for /# IStaff #/, could you please call me back"
return (
<div>
{modalMessage
.split("/")
.map((text, idx) =>
text.includes("#") ? this.replaceCharacter(idx, text) : text,
)}
</div>
)
}
handleChange(e) {
let arrayString = []
arrayString.splice(Number(e.target.name), 0, e.target.value)
this.setState({ editedArray: arrayString })
console.log(arrayString)
}
replaceCharacter(idx, text) {
let formattedText = text.replace(/#/g, " ")
return (
<input
key={idx}
name={idx}
defaultValue={formattedText}
onChange={e => this.handleChange(e)}
/>
)
}
I think you would be better off storing your data in a keyed object rather than in an array.
So, add some default state:
state = {
editedData: {}
}
Then, in your handleChange, set the editedData object to be the last iteration but overrwrite the index key with the latest value like:
this.setState({
editedData: { ...this.state.editedData, [e.target.name]: e.target.value }
})
Then, if you log out that object, you will have something like {1: " Ann adsadasd", 3: " IStaff adasdasdasd"} where each key will correspond to the index in the array of editable data so it would be easy to mutate that back into an array.
This is definitively not a React way of doing things. It is too overly complex for something that React handles very efficiently.
You are not utilising state. Changes to state re-render the component so it would update for you easily.
You are sending the item inside onClick, adding a lambda function to render, where you should pass a reference and use dataset/value.
The most basic example of how to achieve this for a single item is:
class TodoApp extends React.Component {
state = {
name: '',
company: ''
};
renderEditModalText() {
const { name, company } = this.state;
return (
<div>
Hello, my name is{' '}
<input name="name" value={name} onChange={this.handleChange} placeholder="enter name" />. I'm
working for{' '}
<input name="company" value={company} onChange={this.handleChange} placeholder="enter company name" />,
could you please call me back
</div>
);
}
handleChange = e => {
const { name, value } = e.target;
this.setState({ [name]: value });
};
render() {
return <div>{this.renderEditModalText()}</div>;
}
}
ReactDOM.render(<TodoApp />, document.querySelector('#app'));
I created select option using ant design .But I need create editable cell inside the select option.
This my select option code
<Select
showSearch
style={{ width: 400 }}
placeholder="Select a Bank"
optionFilterProp="children"
onChange={this.handleChange.bind(this)}
>
<option value="1">Bank1</option>
<option value="2"> Bank2</option>
<option value="3"> Bank3</option>
</Select>
And onChange functions is
handleChange(value) {
console.log(`selected ${value}`);
this.setState({
bank:value,
});
}
Can you help me?
I suppose the question is whether or not this is an editable list.
The Select component has a mode prop that can be used to change the functionality with the following options:
'default' | 'multiple' | 'tags' | 'combobox'
Using the tags mode would allow you to add and remove items and generate a tokenized list when the form is submitted.
If you are looking at a fixed list and then wanting to create new items to add to the list:
If you want to be able to add new items to the list, this doesn't exist currently, as far as I am aware.
You may be able to refashion something from the Ant Design Pro components, or otherwise come up with a solution where:
when "create" is selected, you toggle the Select for an Input
when the input is submitted/blurred update the Options list, toggle the Select/Input once more and submit the value to the back-end.
I hope this helps.
You don't need to do that actually. All you need to do is to use component state and two simple callback functions ant design provides for select.
So let's assume you need to allow users not to also search for existing values inside a Select but if it didn't exist they can choose a new one. So here's what I'd do:
Inside render() method:
<Select
showSearch
value={this.title}
filterOption={true}
onSearch={this.handleSearch}
onFocus={this.handleFocus}
style={{ width: "100%" }}>
{this.titles.map((title) => (
<Select.Option key={title}>{title}</Select.Option>
))}
</Select>
Where this.titles = ["something", "else"].
Then Inside this.handleSearchand this.handleFocus I'd write:
protected handleSearch = (value: string) => {
this.setState({ titles: value && value !== "" ? [...this.titles, value] : fileTitles });
};
protected handleFocus = () => {
this.setState({ this.titles });
};
What we're basically doing is to populate the options we're iterating over inside the Select with this.titles in the state of the component itself (don't confuse it with Redux or MobX) when user opens the selector and once user searches for anything that would be added to options as well. With this approach you won't need an input or a switch to show/hide inputs. Hope it helps.
You could use another modal to input the additional value.
Check this : https://codesandbox.io/s/antdselectaddoption-7fov7
Code from mamsoudi throws Errors, so i took his idea and made my own component that i'm sharing with you.
import React from 'react';
import {Select} from "antd";
class FieldSelectAndCustomText extends React.Component {
constructor(props) {
super(props);
this.initialTitles = ["something", "else"];
this.state = {
titles: this.initialTitles,
currentValue: null,
};
}
handleSearch = (value) => {
const titles = this.state.titles;
for (let i = 0; i < titles.length; i++) {
const isSearchValueInState = new RegExp(value).test(titles[i]);
if (!isSearchValueInState) {
this.setState({
titles: [...this.initialTitles, value],
currentValue: value
});
break;
}
}
};
handleChange = (value) => {
this.setState(prev => ({...prev, currentValue: value}));
}
render () {
return (
<div>
<Select
showSearch
value={this.state.currentValue}
filterOption={true}
onSearch={this.handleSearch}
onChange={this.handleChange}
onFocus={this.handleFocus}
style={{ width: "100%" }}>
{this.state.titles.map((title) => (
<Select.Option value={title} key={title}>{title}</Select.Option>
))}
</Select>
</div>
);
}
}