React-select multi select update a prop when no value is selected - javascript

I am using react-select library to create a multi-selection menu. When there is no value selected I want a certain style width to be used on the div and when a user starts selecting values from the select dropdown, I want this width to be set to null so that the select component can use it's own auto adjusting width capability. I have the below code but I can't get the width to update synchronously as it updates after the next render call. Not sure how to make it update with the new width immediately. I know setState is async but if you use a callback in the setState function I would imagine it would render with the new state.
...
constructor(props) {
super(props);
this.state = {
dropBoxWidth: { width: 130 },
selectLength: 1
}
}
.....
handleChange = selectedOption => {
if (this.state.selectLength > 0) {
this.setState(
{ selectedOption, selectLength: selectedOption.length, dropBoxWidth: null },
() => console.log(`Option selected:`, this.state.selectedOption, this.state.selectLength, this.state.dropBoxWidth)
);
} else {
this.setState({ dropBoxWidth: { width: 130 }, selectLength: 1 }), () =>
console.log("New Dropbox Width ", this.state.dropBoxWidth)
}
};
render() {
return (
<div style={this.state.dropBoxWidth}>
<Select
closeMenuOnSelect={false}
isMulti
options={aList}
onChange={this.handleChange}
placeholder="Item Select"
/>
</div>
)
}
Again to be clear, I want the style width of the div to be set to 130 when there is no value selected. This can be when the page is opened or refreshed(constructor props has the width to 130), and if a user selects values then decides to clear all the selections from the menu.

You can use inital state in styles of component.
<Select
options={options}
styles={{
container: (provided, state) => ({
...provided,
width: !state.hasValue && "130px",
borderBottom: "1px dotted pink"
})
}}
/>
Like here: https://codesandbox.io/s/amazing-dawn-xwcld?file=/src/App.js:290-517
React select styles doc: https://react-select.com/styles

The reason your console.log isn't printing what you'd expect as the dropbox width is that it's not actually being passed as a callback.
If you format the setState call a bit, you can notice that the comma is actually after the closing parenthesis:
this.setState({
dropBoxWidth: { width: 130 },
selectLength: 1
}),
() => console.log("New Dropbox Width ", this.state.dropBoxWidth);
But the issue with the code is that you're using stale data when doing the this.state.selectLength comparison. You're not really interested in what the value was previously, but what the value is currently. The selectedOption parameter which react-select passes to you is either an array, or null, so you can check the length from there directly (i.e. instead of this.state.selectLength > 0, you can do selectedOption && selectedOption.length > 0).
However, since you mentioned that you're interested in the style and length because you want to style the parent div, you don't really need to store the style or the length - you can just store the selected option and derive the state in render():
const selectedStyle = { width: 130 }
class Selector extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null,
};
}
handleChange = (selectedOption) => {
// null or object array
this.setState({ selectedOption });
};
render() {
const { selectedOption } = this.state;
const anythingSelected = selectedOption && selectedOption.length > 0;
return (
<div style={!anythingSelected ? selectedStyle : null}>
<Select
closeMenuOnSelect={false}
isMulti
options={aList}
onChange={this.handleChange}
placeholder="Item Select"
/>
</div>
);
}
}

Related

React - state doesn't update in class component even though the message printed on the screen changes

I have an App component which holds an input. Every time I type in the input, the value of the input updates and a Message component prints a different message, depending on how long the input is. At the same time, a third component called Character print to the screen every letter of the string, individually. The desired behavior is that when I click on one of the letters, it gets removed from the string, the new string is displayed on the screen and the input also gets updated with the new string.
I used some console.logs to debug and everything seems to be happening as expected, until the last step when I am trying to update the state, but for some reason, it doesn't get updated.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { text: "" };
}
render() {
const handleUpdateText = event => {
this.setState({
text: event.target.value
});
};
const inputLength = this.state.text.length;
const toArray = this.state.text.split("");
const handleDeleteLetter = index => {
toArray.splice(index, 1);
console.log(toArray);
const updatedArray = toArray.join("");
console.log(updatedArray);
this.setState({ text: updatedArray });
console.log(this.state.text);
};
return (
<>
<input type="text" onChange={handleUpdateText} />
<Message inputLength={inputLength} />
{toArray.map((letter, index) => (
<Character
key={index}
theLetter={letter}
deleteLetter={() => handleDeleteLetter(index)}
/>
))}
</>
);
}
}
class Message extends React.Component {
render() {
const { inputLength } = this.props;
let codeToPrint = "The text is long enough!";
if (inputLength <= 5) {
codeToPrint = "The text is not long enough!";
}
return <p>{codeToPrint}</p>;
}
}
class Character extends React.Component {
render() {
const { theLetter, deleteLetter } = this.props;
return (
<div
style={{
display: "inline-block",
padding: "16px",
textAlign: "center",
margin: "16px",
backgroundColor: "tomato"
}}
onClick={deleteLetter}
>
{theLetter}
</div>
);
}
}
The complete code is here:
https://codesandbox.io/s/react-the-complete-guide-assignment-2-list-conditionals-e6ty6?file=/src/App.js:51-1007
I don't really understand what am I doing wrong and I have a feeling is somehow related to a life cycle method. Any answer could help. Thank you.
State is getting updated, you just need to pass value prop to the input so that input's value can be in sync with your state
<input type="text" value={this.state.text} onChange={handleUpdateText} />
And you're not seeing updated state just after setting it because setState is asynchronous. That's why the console statement just after the setState statement shows the previous value.
Also you should move functions out of your render method, because everytime your component re-renders, new functions would be created. You can declare them as class properties and pass their reference
handleUpdateText = event => {
this.setState({
text: event.target.value
});
};
render() {
.......
return (
<>
<input type="text" onChange={this.handleUpdateText} />

Access to DOM elements by ID in React

I want to handle disable attribute of input elements by ID in my React app
It's possible with states but number of elements is not fixed, maybe 10 input or 20 or more ...
I've decided to set ID for each input and access to them with ID, for example :
document.getElementById('title-12') ....
So, is it a suitable trick or best practice to handle this issue ?
Performance and clean code is very important for me :-)
Thanks
Oops... my bad. I digged into your discussion and here's a new solution.
That's still correct that React approach is advised so we should use reusable components for inputs (we can have any number of inputs right now). All the input's data are stored in parent component's store as a collection. We map through collection and send properties to each component (in the simplest version - id, isDisabled and disableInput() function).
class Titles extends Component {
constructor(props) {
super(props);
this.state = {
titles: [
{
id: 0,
disabled: true
},
{
id: 1,
disabled: false
}
]
};
}
addNewInput = () => {
const prevList = this.state.titles;
const newItem = {
id: prevList.length,
disabled: false
};
this.setState({ titles: [...prevList, newItem] });
};
disableInput = id => {
const titles = this.state.titles;
titles[id].disabled = !titles[id].disabled;
this.setState({ titles });
};
render() {
return (
<div>
<h1>Titles list</h1>
<form style={{ display: "flex", flexDirection: "column" }}>
{this.state.titles.map(title => (
<Title
key={title.id}
id={title.id}
isDisabled={title.disabled}
disableInput={id => this.disableInput(id)}
/>
))}
</form>
<button onClick={() => this.addNewInput()}>Dodaj nowy</button>
</div>
);
}
}
in Title component we just render the props in to <input> and a button with onClick function that sends id of this component to its parent, where disable attribute's value is being reversed.
const Title = ({ id, isDisabled, disableInput }) => {
return (
<div>
<input
id={id}
type="text"
placeholder={id}
disabled={isDisabled}
/>
<button type="button" onClick={() => disableInput(id)}>
disable input
</button>
</div>
);
};
Working example can be found here.
Please let me know if it works for you.

How to show tooltips for react-select?

I need to show tooltips for react-select container (not for a separate options) using react-tooltip library.
I made my own SelectContainer component based on the original one and added there data-tip and data-for HTML attributes. Tooltip shows up but when I change selected value it disappears and is not displayed any more.
Here is my code:
const getSelectContainer = options => props => {
return (
<components.SelectContainer
{...props}
innerProps={{
...props.innerProps, ...{
'data-tip': options.tooltipText,
'data-for': options.tooltipId,
}
}}
/>
)
}
const CustomSelect = (props) => {
const tooltipId='tooltip_id'
const tooltipText='tooltip'
const selectedOption = colourOptions.filter(option => option.value === props.value)
return (
<div>
<ReactTooltip effect="solid" html={true} place="bottom" id={tooltipId} />
<Select
defaultValue={colourOptions[4]}
value={selectedOption}
options={colourOptions}
classNamePrefix="react-select"
onChange={item => props.onChange(item.value)}
className="my-select"
components={{
SelectContainer: getSelectContainer({
tooltipText:tooltipText,
tooltipId:tooltipId
})
}}
/>
</div>
)
}
class Page extends Component {
constructor (props) {
super(props)
this.state = {
selectValue: 'red'
}
}
render () {
const onChange = (value)=> {
this.setState({selectValue: value})
//alert(value)
}
return (
<CustomSelect
value={this.state.selectValue}
onChange={onChange}>
</CustomSelect>
)
}
}
See full example here:
If I wrap Select with another <div> and assign tooltip HTML attributes to it everything works correctly but I don't want to add one more DOM element just for that.
What can I do to show tooltips after changing selection?
Try rebuilding the react-tooltip when the state changes.
useEffect(() => {
ReactTooltip.rebuild();
}, [state]);

How do I identify a material-ui slider in react?

I want multiple material-ui sliders in one react component sharing a common event handler. However, to make this work, I would need to identify the originating slider. From the API documentation I can't see how that is achieved. I've tried applying id and name attributes to the <Slider>-component, yet I'm not seeing these in the synthesized event in the event handler.
handleChange = (event, value) => {
console.log(event); // 'Id' and 'name' attributes in 'target' are empty
this.setState({ value });
};
render() {
const { classes } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<Typography id="label">Slider label</Typography>
<Slider
classes={{ container: classes.slider }}
value={value}
aria-labelledby="label"
onChange={this.handleChange}
/>
</div>
);
}
This is fetched from the official demo project:
https://codesandbox.io/s/4j9l9xn1o4
Any help would be greatly appreciated!
You can format your state like so:
state = {
slider1: 50, //slider1 is the name of the first slider
slider2: 50, //slider2 is the name of the second slider
}
After that, you have 2 ways to set the state when the value of the slider is changed:
(Update: This method doesn't work! However I will leave it here for future reference) By using HTML attribute id, then access it by using event.target.id. The whole handleChange method would look like this:
handleChange = (e, value) => {
this.setState({
[e.target.id]: value
});
}
By passing then name of the slider straight to the handleChange method, and it would be like this:
handleChange = name => (e, value) => {
this.setState({
[name]: value
});
}
Overall, your component should be:
class SimpleSlider extends Component {
state = {
slider1: 50,
slider2: 50
};
handleChange = name => (e, value) => {
this.setState({
[name]: value // --> Important bit here: This is how you set the value of sliders
});
};
render() {
const { classes } = this.props;
const { slider1, slider2 } = this.state;
return (
<div className={classes.root}>
<Typography id="label">Slider label</Typography>
<Slider
classes={{ container: classes.slider }}
value={slider1}
aria-labelledby="label"
onChange={this.handleChange("slider1")}
/>
<Slider
classes={{ container: classes.slider }}
value={slider2}
aria-labelledby="label"
onChange={this.handleChange("slider2")}
/>
</div>
);
}
}
See it in action: https://codesandbox.io/s/4qz8o01qp4
Edit: After running the code I found that the #1 doesn't work because the id attribute is not being passed down to the event target

How to add the input field inside the select option using ant design and react

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>
);
}
}

Categories

Resources