Dropdown functions normally until I add onClick(). I guess Semantic UI has some built in functions for onClick() so when I call it, the item does not show up as selected choice with the option to "x" out.
onChange() does not work.
class Wines extends Component {
state = {
wines: [],
list: []
};
handleDropdown = (event, {value}) => {
this.setState({ list: value})
console.log(value)
}
render() {
const options = [
{ key: 'France', text: 'France', value: 'France', onClick: this.handleDropdown },
{ key: 'Spain', text: 'Spain', value: 'Spain'},
{ key: 'Portugal', text: 'Portugal', value: 'Portugal' }
]
return (
<Dropdown placeholder='Countries' fluid multiple selection options={options} />
)
}
}
export default Wines;
More likely than not, it is working as intended. I checked the docs of the Semantic UI dropdown here. It doesn't do anything in that regard for you in a magic-y way.
One issue is that you aren't building a list inside your click handler. You just keep replacing the value you just clicked on. You need to build on top each time
setState({ list: [...this.state.list, value] })
As mentioned before, it doesn't do any magic for you, so you have to modify the props yourself, based on the items that you have placed in this.state.list. I suggest mapping over the array again, so you don't have to repeatedly hard code the equality check for every entry.
const options = [
{ key: 'France', text: 'France', value: 'France', onClick: this.handleDropdown },
{ key: 'Spain', text: 'Spain', value: 'Spain'},
{ key: 'Portugal', text: 'Portugal', value: 'Portugal' }
]
const optionsToRender = options.map(dropdownEntry => {
if (this.state.list.includes(dropdownEntry.value)) {
dropdownEntry.somePropYouWantToChange = 'some value you want to assign it to'
}
return dropdownEntry
})
<Dropdown placeholder='Countries' fluid multiple selection options={optionsToRender} />
Related
I've data coming from Redux in this format:
[
0: {value: '1.5', label: 'Extra cheese'}
1: {value: '3', label: 'Tomato'}
]
and i try to load them into my react-select.
But it fails, bcs it loads instantly the initialToppings as defaultValue (So it shows me empty Strings as defaultValue). And this Value can never be changed again. But without initialToppings i get nothing at defaultValue bcs redux is to slow and the defaultValue is empty so i can't load it in again later...
const initialToppings = [{ label: '', value: '' }];
const [defaultToppings, setDefaultToppings] = useState(initialToppings);
useEffect(() => {
setDefaultToppings(
editProduct?.selectedToppings?.map((topping, value) => ({
...topping,
value,
})) ?? initialToppings
);
}, [editProduct]);
<Select
options={extraOptions}
formatOptionLabel={formatExtras}
isMulti
defaultValue={defaultToppings}
// defaultValue={[
// { label: 'Test 1', value: '1' },
// { label: 'Test 2', value: '2' },
// ]}
onChange={setSelectedToppings}
/>
You can add key props to Select to force remounting component and make it re-render
<Select
key={defaultToppings}
options={extraOptions}
formatOptionLabel={formatExtras}
isMulti
defaultValue={defaultToppings}
// defaultValue={[
// { label: 'Test 1', value: '1' },
// { label: 'Test 2', value: '2' },
// ]}
onChange={setSelectedToppings}
/>
I've a simple codesandbox, you can check it
I am dynamically adding and removing Dropdowns based on the index of the array. I am setting the index using. When adding or removing a dropdown, I increment and decrement that index. The goal would be an array that looks something like this:
[
{ index: 0, type: "facebook" },
{ index: 1, type: "instagram" }
]
The problem is when I add a new dropdown, the index is incrementing by 2 or 3 instead of 1, resulting in the following output.
[
{ index: 0, type: "facebook" },
{ index: 2, type: "instagram" }
]
Here is the code for my component:
class SocialProfileComponent extends Component {
constructor(props) {
super(props);
let index = 0;
this.state = {
options: [
{value: 'email', label: 'Email'},
{value: 'instagram', label: 'Instagram'},
{value: 'linkedin', label: 'Linkedin'},
{value: 'pinterest', label: 'Pinterest'},
{value: 'skype', label: 'Skype'},
{value: 'tiktok', label: 'TikTok'},
{value: 'tumblr', label: 'Tumblr'},
{value: 'twitter', label: 'Twitter'},
{value: 'whatsapp', label: 'WhatsApp'},
{value: 'wordpress', label: 'Wordpress'},
{value: 'youtube', label: 'Youtube'},
{value: 'other', label: 'Other'},
],
socialDetails: [
{
index: index,
type: "",
link: "",
}
]
};
}
addNewRow = (e) => {
this.setState(prevState => ({
socialDetails: [
...prevState.socialDetails,
{
index: index++,
type: "",
link: "",
}
]
}));
}
deleteRow = (index) => {
this.setState({
socialDetails: this.state.socialDetails.filter(
(s) => index !== s.index
)
})
}
clickOnDelete(record) {
this.setState({
socialDetails: this.state.socialDetails.filter(r => r!==record)
})
}
componentDidMount() {
}
render = () => {
let {socialDetails, options} = this.state;
const {onInputChange} = this.props;
return (
<>
<SocialProfile
label={`Social profiles`}
addRow={this.addNewRow}
deleteRow={this.deleteRow}
socialDetails={socialDetails}
options={options}
onInputChange={onInputChange}
formKey={'socialprofiles'}
createKeyValue={this.createNewKeyValuePair}
placeholder={'Select'}
/>
</>
)
}
}
Code Sandbox
The behavior you are experiencing where the index increases by 2 or 3 is a result of React strict mode. In addition to other things, strict mode helps detect unexpected side effects to help you prepare your app for the upcoming concurrent mode feature. In concurrent mode, React will break up rendering into smaller chunks pausing and resuming work as necessary. This means that render phase lifecycle methods can be run more than once.
To help you prepare for this upcoming behavior of concurrent mode, strict mode will intentionally invoke render phase lifecycles twice to identify potential side effects. State updater functions are one instance of this, meaning that calling index++ in your state updater function will be run twice in strict mode.
The easiest solution would be to simply assign the new index to a variable before calling this.setState so that your state updater function is idempotent and can be called more than once.
addNewRow = (e) => {
const newIndex = ++this.index
this.setState((prevState) => ({
socialDetails: [
...prevState.socialDetails,
{
index: newIndex,
type: "",
link: ""
}
]
}));
};
I have a dropdown list with 5 options. I need to save the selected option to my state as listValue.
My list of options and the state
const options = [
{ key: 1, text: 'OK', value: 1 },
{ key: 2, text: 'Avvikelse', value: 2 },
{ key: 3, text: 'Ej Relevant', value: 3 },
{ key: 4, text: 'Observation', value: 4 },
{ key: 5, text: 'Saknas', value: 5 },
]
export default class ConfirmationModal extends React.Component {
state = {
listValue: 'Status'
}
My list (from semantic-ui)
dropDownList = () => (
<Dropdown
placeholder={this.state.listValue}
clearable
options={options}
selection
/>
)
How can I store the selected option in my state?
You can add an onChange handler and put the value given to the handler in your component state.
Example
const options = [
{ key: 1, text: "OK", value: 1 },
{ key: 2, text: "Avvikelse", value: 2 },
{ key: 3, text: "Ej Relevant", value: 3 },
{ key: 4, text: "Observation", value: 4 },
{ key: 5, text: "Saknas", value: 5 }
];
class DropdownExampleControlled extends React.Component {
state = {
options,
value: options[0].value
};
handleChange = (_e, { value }) => this.setState({ value });
render() {
const { value, options } = this.state;
const currentOption = options.find(o => o.value === value);
return (
<Grid columns={2}>
<Grid.Column>
<Dropdown
onChange={this.handleChange}
options={options}
placeholder="Choose an option"
selection
value={value}
/>
</Grid.Column>
<Grid.Column>
<Segment secondary>
<pre>Current value: {currentOption.text}</pre>
</Segment>
</Grid.Column>
</Grid>
);
}
}
As I understood you, you need to save in the state the values, which user selected?
If yes, You need have an event for example onChange, which means that user seelct that particular option from list. and set it in state
onChange(selectedValue) {
this.setState({listValue: selectedValue});
}
add this function to your component:
handleChange = (_p, { value }) => {
this.setState({ listValue: value});
};
add it as prop to your Dropdown:
<Dropdown onChange={this.handleChange} placeholder={this.state.listValue} clearable options={options} selection />
Handle state management using setState. Simple example:
Example Sandbox
const options = [
{ key: 1, text: 'OK', value: 1 },
{ key: 2, text: 'Avvikelse', value: 2 },
{ key: 3, text: 'Ej Relevant', value: 3 },
{ key: 4, text: 'Observation', value: 4 },
{ key: 5, text: 'Saknas', value: 5 },
]
class App extends Component {
constructor(props){
super(props)
this.state = {
listValue: ''
}
}
componentWillMount() //before render
{
this.setState({
listValue: options[1].text //storing text from second line
})
}
render() {
return (
<div>
{this.state.listValue}
</div>
);
}
}
This displays: Avvikelse
I am working on React Table. I am basically a beginner in React. I have a dashboard page where I display a React Table of 8 columns. I have a customize button which will open a popup page, this popup page has 8 check boxes allows me to show/hide those React columns. Initially all the check boxes in this popup page is set to true. When I uncheck a column that particular column get disabled.
The first image only has columns (no sub columns are there but in real life I will have to use sub columns) . Also in the 1st image there are 8 check boxes. It is 10 in real life. The 2nd image basically shows the basic columns. I am basically telling you as this images do not 100% correlate to my functionality. It shows a basic idea
This is my column data (total 8 columns, column 1 to 5 and column 7 has one sub column while column 6 & column 8 has multiple sub columns)
I need to apply show/hide filter on the level 1 header (super header) and level 2 headers of column 1 to 5. Column 6 to 8 are always visible.
It is to be noted that I have a constant file in my project. Whenever I use the global constants of that file in other components I import it.
It should be mentioned that React Table has a property called show (default value is true but we can hide a column by setting it false)
const Constants = {
columnShowHide: [
{ id: 1, value: true },
{ id: 2, value: true },
{ id: 3, value: true },
{ id: 4, value: true },
{ id: 5, value: true },
{ id: 6, value: true },
{ id: 7, value: true },
{ id: 8, value: true },
{ id: 9, value: true },
{ id: 10, value: true },
]
};
export { Constants as default };
This is the code for the popup page for checkbox
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { ActionCreators } from '../../../actions';
import ButtonComponent from '../../common/button/ButtonComponent';
import { CheckBox } from '../../common/chkbox/CheckBox';
import Constants from '../../../util/constants';
class CustomizedView extends Component {
constructor(props) {
super(props);
this.handleCheckChildElement = this.handleCheckChildElement.bind(this);
this.state = {
items: [
{ id: 1, value: 'Sub Column 1', isChecked: true },
{ id: 2, value: 'Super Column 1', isChecked: true },
{ id: 3, value: 'Sub Column 2', isChecked: true },
{ id: 4, value: 'Super Column 2', isChecked: true },
{ id: 5, value: 'Column 5', isChecked: true },
{ id: 6, value: 'Column 6', isChecked: true },
{ id: 7, value: 'Column 7', isChecked: true },
{ id: 8, value: 'Column 8', isChecked: true },
{ id: 9, value: 'Column 7', isChecked: true },
{ id: 10, value: 'Column 8', isChecked: true },
]
};
}
handleClick() {
this.setState({ isChecked: !this.state.isChecked });
}
handleCheckChildElement(event) {
const { items } = this.state; //extract state values like this to a const variable
const newItems = items.map((item) => { //do map on items because map returns a new array. It’s good practice to use .map than forEach in your case
if(item.value === event.target.value) {
item.isChecked = event.target.checked;
return item; //return updated item object so that it will be pushed to the newItems array
}
return item; // return item because you need this item object as well
});
this.setState({ items: newItems }); //finally set newItems array into items
Constants.columnShowHide[0].value = items[0].isChecked;
Constants.columnShowHide[1].value = items[1].isChecked;
Constants.columnShowHide[2].value = items[2].isChecked;
Constants.columnShowHide[3].value = items[3].isChecked;
Constants.columnShowHide[4].value = items[4].isChecked;
Constants.columnShowHide[5].value = items[5].isChecked;
Constants.columnShowHide[6].value = items[6].isChecked;
Constants.columnShowHide[7].value = items[7].isChecked;
console.log('From constant file after ' + JSON.stringify(Constants.columnShowHide));
}
render() {
return (
<div className='div-container-custom' >
<div className='bottomBar'>
<ButtonComponent
text='Apply'
className='activeButton filterMargin-custom'
width='100'
display='inline-block'
onClick={() => { this.props.applyFilter(this.state, false); }}
/>
<ButtonComponent
text='Clear Filter'
className='greyedButton clear-custom-filter'
width='100'
display='block'
marginTop='60'
onClick={() => { this.props.clearFilter(this.state, true); }}
/>
</div>
<div>
<div className='data-points-text'>
<span> Columns </span>
</div>
<div className="App">
<ul>
{
this.state.items.map((item, i) => {
return (<div key={i} ><CheckBox handleCheckChildElement={this.handleCheckChildElement} {...item} /></div>);
})
};
</ul>
</div>
</div>
</div>
);
}
}
CustomizedView.propTypes = {
applyFilter: PropTypes.func.isRequired
};
CustomizedView.defaultProps = {
};
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomizedView);
Now I need to use the constant to moduify my ReactTableComponent
This is my column data in React Table component
const columns = [
{
Header: 'Column 1',
columns: [
{
Header: 'S Column 1',
accessor: 'firstName',
show : some_var (if this is true,column/sub column in shown, else hidden )
}
]
},
{
Header: 'Column 2',
columns: [
{
Header: 'S Column 2',
accessor: 'firstName'
}
]
},
{
Header: 'Column 3',
columns: [
{
Header: 'S Column 3',
accessor: 'firstName'
}
]
},
{
Header: 'Column 4',
columns: [
{
Header: 'S column 4',
accessor: 'firstName'
}
]
},
{
Header: 'Column 5',
columns: [
{
Header: 'S column 5',
accessor: 'firstName'
}
]
},
{
Header: 'Column 6',
columns: [
{
Header: 'S column 6a',
accessor: 'firstName'
},
{
Header: 'S column 6b',
accessor: 'firstName'
},
{
Header: 'S column 6c',
accessor: 'firstName'
},
{
Header: 'S column 6d',
accessor: 'firstName'
}
]
},
{
Header: 'Column 7',
columns: [
{
Header: 'S column 7',
accessor: 'firstName'
}
]
},
{
Header: 'Column 8',
columns: [
{
Header: 'S Column 8a',
accessor: 'firstName'
},
{
Header: 'S Column 8b',
accessor: 'firstName'
},
{
Header: 'S Column 8c',
accessor: 'firstName'
},
{
Header: 'S Column 8d',
accessor: 'firstName'
}
]
},
];
Now I have the 10 boolean flags in Constants.js which I can import in any component. It is available across the project. I have checked by consoling.
Initially all the flags are true so when the checkbox popup page opens, so when I uncheck and press apply button that particular columns/sub columns will be hidden.
The checkbox values can be brought to initial state by clearFilters function. All columns and sub columns should show after clearFilter
Now since I am complete beginner In React, what should I write in the function of Apply and clear button to implement my desired idea. I have some incorrect functions. Also after pressing applyFilter/clearFilter I need to update the global constants and use them in ReactTableComponent to use the show property of ReactTable to show/hide columns/sub columns.
I am not using Redux or any complex things. I have a working constants file and I want to use that to implement this. Kindly help me in achieving this
I want to add sub categories with react on a list, but when i click on sub cat, i have two events : First on sub and second on parent category.
How can i have only child category ?
There is my actual code :
getList(myList){
return myList.map((item) => {
let subList = '';
if (item.hasSub){
subList = (<ul>{this.getList(item.sub)}</ul>)
}
return (
<li onClick={() => {this.props.clicHandler(item.id)}}>{item.title}<div>{subList}</div></li>);
})
}
I'm using recursive method to create my list.
My actual array is like this :
this.list.push({id: 1, title:'coucou' , hasSub: false});
this.list.push({id: 2, title:'toto' , hasSub: false});
this.list.push({id: 3, title: 'cat', sub: [{id:4, title:'titi' , hasSub: false}, {id:5, title:'tutu' , hasSub: false}] , hasSub: true});
Thank you for your help !
The problem is that click events "bubble" up to parent elements. The solution is to call Event.prototype.stopPropagation on it.
In the below snippet I've added a clickHandler method to the component, and in getList I pass both the event (evt) and item.id to it. In that method I call evt.stopPropagation() before calling this.props.clickHandler. However, you could also do this in the parent component's clickHandler method, or within getList. Choose whatever best suits your use case.
class App extends React.Component {
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
}
render() {
return <ul>{this.getList(this.props.items)}</ul>;
}
getList(list) {
return list.map((item) => {
const subList = item.hasSub && (<ul>{this.getList(item.sub)}</ul>);
return (
<li key={item.id} onClick={evt => {this.clickHandler(evt, item.id)}}>{item.title}<div>{subList}</div></li>
);
});
}
clickHandler(evt, itemId) {
// Stop event propagatation before calling handler passed in props
evt.stopPropagation();
this.props.clickHandler(itemId);
}
}
const data = [
{ id: 'a', title: 'A', hasSub: false },
{ id: 'b', title: 'B', hasSub: true, sub: [
{ id: 'b1', title: 'B1', hasSub: true, sub: [
{ id: 'b1a', title: 'B1a', hasSub: false },
] },
{ id: 'b2', title: 'B2', hasSub: false },
] },
{ id: 'c', title: 'C', hasSub: true, sub: [
{ id: 'c1', title: 'C1', hasSub: false },
{ id: 'c2', title: 'C2', hasSub: false },
] },
];
function clickHandler(itemId) {
console.log('clicked item', itemId);
}
ReactDOM.render(<App items={data} clickHandler={clickHandler}/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div></div>
P.S. Your code is excellent, but might I recommend a somewhat more "React way" to render nested items?
What your component's render method does is call a getItems function that in turn calls itself, recursively. However, if you recall that most React components can themselves be thought of as functions, you can refactor your code so that your render method instead renders an <Items> component that recursively renders instances of itself.
This approach tends to yield smaller, functional components that are more reusable and easier to test. Take a look in the snippet below.
const Items = ({items, onClick}) => (
<ul>
{items.map(item => (
<li key={item.id} onClick={evt => onClick(evt, item.id)}>
{item.title}
{item.hasSub && <Items items={item.sub} onClick={onClick}/>}
</li>
))}
</ul>
);
const data = [
{ id: 'a', title: 'A', hasSub: false },
{ id: 'b', title: 'B', hasSub: true, sub: [
{ id: 'b1', title: 'B1', hasSub: true, sub: [
{ id: 'b1a', title: 'B1a', hasSub: false },
] },
] },
];
function clickHandler(evt, itemId) {
evt.stopPropagation();
console.log('clicked item', itemId);
}
ReactDOM.render(<Items items={data} onClick={clickHandler}/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div></div>
You need to prevent the event propagation :
getList(myList){
return myList.map((item) => {
let subList = '';
if (item.hasSub){
subList = (<ul>{this.getList(item.sub)}</ul>)
}
return (
<li onClick={(event) => {event.preventDefault(); this.props.clicHandler(item.id)}}>{item.title}<div>{subList}</div></li>);
})
}
This way the first item to catch the click will stop the click propagation.