I am implementing a custom input element similar to React Select. Users will be able to type something into the input element and select a suggestion from a dropdown (in my case, I implemented a component called SuggestionList which serves the same purpose as that dropdown suggestion list).
Currently, the SuggestionList is a sibling React component to the input element that the user types into (I named it CustomInput).
I want SuggestionList to disappear when CustomInput loses focus, so I implemented an onBlur handler which makes SuggestionList either un-mount or have "display" CSS property set to none. However, the problem is that after SuggestionList disappears, its onClick/handleClick event handler doesn't get called, and that event handler is responsible for adding user-selected elements to the selected items list.
Code shown below. There is more explaination after this code.
const AutocompleteInput = ({...}) => {
const [suggestionVisible, setSuggestionVisible] = useState(true);
return (
....
<SystemComponent position="relative">
<SuggestionList p={theme.space[4]}
position="absolute"
visible={suggestionVisible}
value={value}
handleClick={handleSelect}
ref={suggestionBox}
/>
<CustomInput
variant="text"
placeholder={placeholder}
value={value}
onChange={(evt) => handleInputChange(evt.target.value)}
onKeyDown={handleKeyDown}
onFocus={ () => {setSuggestionVisible(true)} }
onBlur={ () => {setSuggestionVisible(false);} }
/>
</SystemComponent>
....
)
}
Therefore, when a user clicks something on the SuggestionList, my CustomInput immediately loses focus, causing SuggestionList to either be unmounted/disappear, and thus its event handler isn't called. As a result, the item the user selects isn't added to the suggestion list.
Having that said, I still want SuggestionList to disappear when the user clicks some other part of the website, thereby causing CustomInput to lose focus. Any suggestions ?
Let's say you are calling onClickHandler() inside your onClick. You can call this inside the onBlur on function.
For example
onBlur={ () => {onClickHandler(); setSuggestionVisible(false);} }
Related
It's a basic question but I searched for a guide without success...
I want to have a list of dropdowns and inputs and each dropdown will change the input next to it.
var list = [{ name: "foo1"}, {name: "foo2"}];
return (
{list.map( (name) => {
return (<div>
<dropdown data={someData}
onChange={(ev) => {
if(ev.value == 'clearIt')
<CHANGE THE NEAR INPUT VALUE>
}
}/>
<input value={name.name} />
</div>)
})});
I don't want to use DOM nor ref cause I understood that it's better to avoid it.
Any suggestions?
Or maybe the ref is the only option?
Thanks
So you can achieve this by doing the following steps:
Create a new component and move the dropdown and input to this new component.
Add state to your component by following this example:
https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
Add an event listener onChange to the dropdown with an event handler which can update the state you created in the first step. (Remember to bind the handler in the constructor.
Add the new component within the div element of this example you gave and pass the relevant data you need to the new component you created.
This should allow you to update only the input next to the dropdown. Also it allows you to have different data for each dropdown you created.
I have a material-ui Table and have been implementing multi-select functionality on it.
My multi select should behave as follows :
Select checkboxes only appear at the start of a row on hover (when nothing has been selected)
Once one row has been selected, all other checkboxes become permanently visible.
The checkbox in the TableHead will select / deselect all rows in the Table when toggled.
Each row has its own individual unique id.
I maintain a Set called idsOfSelectedRows via a useState hook to keep track of all rows currently selected.
Each TableRow has a Checkbox which looks like this
<Checkbox
className={
idsOfSelectedRows.size === 0 &&
styles.rowSelectionCheckboxStyle
}
onChange={() => handleRowSelectionCheckboxClick}
checked={idsOfSelectedRows.has(row.id)}
/>
and handleRowSelectionCheckboxClick looks like this
const handleRowSelectionCheckboxClick = event => {
const idOfClickedRow = event.target.attributes.getNamedItem("id").value;
//If the checkbox has just become checked then add it to the idOfSelectedRows Set, otherwise remove it.
setIdsOfSelectedRows(
event.target.checked
? new Set(idsOfSelectedRows.add(String(idOfClickedRow)))
: () => {
idsOfSelectedRows.delete(String(idOfClickedRow));
return new Set(idsOfSelectedRows);
}
);
};
My issue is that clicking on the checkboxes on the rows is unresponsive. I can select them all by clicking on the select all Checkbox in the TableHead but clicking on checkboxes in individual rows does not change their state ?
Here's a full CodeSandbox reproducing the exact issue. What do I need to do to make the checkboxes in the rows be toggleable ?
Remove the () => from the onChange assignment for a quick fix.
Why
You're declaring an anonymous inline function, but that function does not call anything (missing () that is syntax to call a function). onChange accepts a function reference. When you give it an inline function, that's the function it will call. The other option is to only pass it the function reference of handleRowSelectionCheckboxClick, and then that function will be called on change instead of the anonymous function middleman.
So your two options are:
onChange={handleRowSelectionCheckboxClick} // Just the reference
OR
onChange={(e) => handleRowSelectionCheckboxClick(e)} // New inline function that calls your function
The second way is unnecessary in most cases. The first is preferred unless you need to pass custom parameters. Here is an example of how it might be useful:
onChange={(e) => handleRowSelectionCheckboxClick(e, id)}
In your sandbox, you map over elements and each gets a change handler. To treat them separately in the handler, you can pass an extra variable to the function like above.
For example, I got your sandbox working by changing the handler and assignment to this:
const handleRowSelectionCheckboxClick = (event, idOfClickedRow) => {
...
onChange={(e) => handleRowSelectionCheckboxClick(e, row.id)}
This line: event.target.attributes.getNamedItem("id").value;
won't give you the required id, since there isn't one passed, change the chekbox like so:
<Checkbox
id={row.id}
onChange={handleRowSelectionCheckboxClick}
checked={idsOfSelectedRows.has(row.id)}
/>
Then, retreive the ID like so:
const idOfClickedRow = event.target.attributes.id.value;
Updated codesandbox:
Like the other answers suggest, change the handleRowSelectionCheckboxClick function call like shown in the example.
If you like your solution with an anonymous function, you should add the (): onChange={() => handleRowSelectionCheckboxClick()}
I am using Material UI Select: https://material-ui.com/components/selects/
The select needs a bit of custom functionality. I have added this in a CodePen sandbox: https://codesandbox.io/s/material-demo-3nzpv
Some MenuItem might need to show a delete icon.
Selecting an option that has the delete icon (but not clicking the delete icon itself) should not show the delete icon after selection as the selected value. The delete icon should still be a part of the MenuItem though.
Clicking on the delete icon should not select the option but trigger the onClick of the delete icon instead. In essence, the default functionality of MenuItem should not trigger, just the icon onClick.
What's the best way I can achieve this?
You need to use renderValue prop from your Select component, in order cu custom render your selected value.
Add the following line to your Select component
renderValue={value => currencies.find(el => el.value === value).label}
You can check an updated version of your working sandbox HERE
Also you can check more on Select props HERE
LE: Update for point 3
In your <DeleteIcon> handler onClick, you need to stop the propagation of the event further. In the handleDelete() function add the event parameter and use e.stopPropagation() method.
const handleDelete = (e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
console.log("Delete");
};
And your component should look like this
<MenuItem key={option.value} value={option.value}>
<div>{option.label}</div>
<div>{option.action && <DeleteIcon onClick={handleDelete}/>}/div>
</MenuItem>
/>
I've also updated a sandbox, with the last version HERE
On your handleChange method, write a condition to change the value only if the value of a menu item does not have the value that does not need to be selected
I am using react fontawesome in my project. Under the render function of my component I have the following which maps a school onto the page and then I want a button with an edit icon to perform an action. My code for the button looks as below:
{ this.state.myschools.map( (school) => (
<Row key={school.id}>
<Col><p>{school.name}</p></Col>
{ school.deaths.length > 0 ? <Col md="2"><button id={school.id} className="likelink" onClick={this.goToSchoolDeathList}>View Deaths</button></Col> : <Col md="2"></Col> }
<Col><button id={school.id} onClick={this.editSchool}><FontAwesomeIcon icon={faEdit} /></button></Col>
</Row>
))}
The editSchool function is as:
editSchool(e) {
console.log("School Id");
console.log(e.target.id);
}
If I click on the empty space around the font awesome icon then the id is logged to the console. If I only click on the area where the icon exits then the id is not logged. I want the user to be able to click on any part of the button including the fa icon and to capture the school id.
I have tried adding the onClick event and the id attribute to the "FontAwesomeIcon" component, but that still doesn't work.
Can anyone help?
Well, this is one of the common pitfall you learn during your time with React. Rest assure, we have all fallen into this pit.
There are few AHA moments here.
1st Aha: React doesn't have a usual way to let you get element attribute in an event (an e in your example).
That being said, you can still access to the button element by e.target and use JavaScript to get attribute.
Solution #1 JavaScript getAttribute
editSchool(e) {
console.log("School Id");
console.log(e.target.getAttribute('id'));
}
e.target will be the HTML Element that you click, and we can use JavaScript way to get attribute. (Please Note that, Javascript way is not React way)
But there are another way and you'll see this way more often.
Solution #2 You can define a function directly in a render
<button id={school.id} onClick={e => this.editSchool(school.id)}>
Please be careful about this another pitfall,
2nd Aha: Bind onClick to function, not result of a function.
Don't do this
<button id={school.id} onClick={this.editSchool(scholl.id)}> // This is incorrect
The thing is onClick prop expect a function to call when someone click.
The first one we define a function e => this.editSchool(school.id) and bind onClick with the newly defined function.
While the second, we just bind onClick to a "result" of function this.editSchool(school.id), which is not a function.
I have a number input field that I'm trying to focus on after receiving input from a bar-code scanner. In reality the scanner is just a pseudo keyboard that I watch for the tab key to get pressed.
In Edge the input receives focus as expected, but in all other browsers tested (Chrome/Firefox) the input doesn't gain focus. Safari has not been testes since I'm on a PC.
// I'm using a lifecycle method to watch for a change to the components props.
// The onInputFocused will unset the value, but I've disabled to this
// to eliminate any possible race cases?!
componentDidUpdate() {
if (this.props.detail.shouldFocus) {
setTimeout(() => {
this.qtyInput.focus();
}, 250);
// this.props.onInputFocused(this.props.detail);
}
}
render(){
return (
...
<input type="number" className="form-control" min="0" name="qty_received" value={detail.ReceiptQuantityReceived}
ref={(input) => { this.qtyInput = input }}
onChange={(e) => { onQtyReceivedChange(e, detail) }}
onBlur={() => { onReceiveShipmentRowBlur(detail) }} />
);
}
I originally was calling focus without a setTimeout, but attempted the timeout based on some suggestions online. I have also attempted to change the input to text, but that didn't have any effect either.
Doing a console.log on this.qtyInput outputs the expected element.
The only other thing I can think of is that the modal that a user scans in to gains it's focus via a similar function, but it has been removed by the time the focus call should be happening.
To add to the weirdness, I've added a console.log to the input's onBlue event and it is getting fired even though you can't type in the field or have any visual indication of focus.
one thing you must try is add this attribute for your INPUT element
(input autoFocus ..)
this attribute may help you