How to toggle dynamically generated dropdowns using the .map functions index? - reactjs

I have a array that for every item in the array a drop down list is dynamically generated. Right now each drop down list share the same toggle boolean so they all open and close and the same time, how can I make this work individually?
I map each object to a index here and then start creating dropdowns:
{Object.keys(props.totalWorkload.options).map((item, i) => (
<WorkloadOptions
key={i}
cnt={i}
appendChoiceList={props.appendChoiceList}
modalDropDown={props.modalDropDown}
toggleDropDown={props.toggleDropDown}
totalWorkloadOptions={props.totalWorkload.options[item]}
/>
))}
When the Drop Down options component is created I pass the index to a function:
<div>
<Dropdown isOpen={props.modalDropDown} toggle={props.toggleDropDown.bind(props.cnt)}>
<DropdownToggle caret>{props.totalWorkloadOptions.optionTitle}</DropdownToggle>
<DropdownMenu>
{props.totalWorkloadOptions.options.map(op => (
// tslint:disable-next-line:no-invalid-this
// tslint:disable-next-line:jsx-no-lambda
<DropdownItem key={op} onClick= {() => props.appendChoiceList(props.totalWorkloadOptions.optionTitle, op)}>
{op}
</DropdownItem>
))}
</DropdownMenu>
<strong> {props.totalWorkloadOptions.optionDescription} </strong>
</Dropdown>
<br />
</div>
The it will arrrive at the following functuion and console log the index and then set the appropriate toggle value in an array to true/false:
toggleDropDown = (index: any) => {
console.log('triggered!:' + index);
let clicked = this.state.modalDropDownClicked;
// tslint:disable-next-line:no-conditional-assignment
if (clicked[index]=!clicked[index]){
this.setState({ modalDropDownClicked: !this.state.modalDropDown[index] });
}
};

I can recommend the following pattern to toggle dynamically created elements:
// Item.js
class Item extends React.Component {
handleClick = () => {
const { id, onClick } = this.props;
onClick(id);
}
render() {
const { isOpen } = this.props;
return (
<li><button onClick={this.handleClick}>{isOpen ? 'open' : 'closed'}</button></li>
)
}
}
// App.js
class App extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
const { items } = nextProps;
if (items !== prevState.prevPropsItems) {
return { items, prevPropsItems: items };
}
return null;
}
state = {
prevPropsItems: [],
items: []
}
toggleItem = id => this.setState(prevState => {
const items = prevState.items.map(item => {
if (item.id === id) {
return { ...item, isOpen: !item.isOpen }
} else {
return item;
}
});
return { items }
})
render(){
const { items } = this.state;
return (<ul>
{items.map(item => <Item key={item.id} id={item.id} onClick={this.toggleItem} isOpen={item.isOpen} />)}
</ul>);
}
}
// AppContainer.js
const itemsFromRedux = [
{ id: '1', isOpen: false },
{ id: '2', isOpen: false },
{ id: '3', isOpen: false },
]
ReactDOM.render(<App items={itemsFromRedux} />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.development.js"></script>
<div id="root"></div>

Related

How to delete an element in list in react?

i am trying to do a simple toDo app with react. I couldnt do how to delete an element in list. Here my code; first state:
class AppForm extends Component {
constructor(props) {
super(props);
this.state = { items: [] , text:''};
this.onChangeHandler=this.onChangeHandler.bind(this)
this.submitHandler=this.submitHandler.bind(this)
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
};
this.setState(state => ({
items: state.items.concat(arrayItem),
text: ''
}));
}
here the problem area. I also tried splice but couldnt.
deleteItem=(index)=>{
let todos= this.state.items.filter(todo => index !== todo.key)
this.setState({
items : todos
})
}
then rendering..
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input type="text" onChange={this.onChangeHandler} value={this.state.text}/>
</form>
<ul>
{this.state.items.map((item,index) =>{
return (
<li key={index}> {item.text}
<p onClick={this.deleteItem.bind(this,index)}> X </p>
</li>
)
})}
</ul>
</div>
);
}
}
export default AppForm;
Splice is the answer.
First, I create a copy of your state array. Then splice it using the index clicked. Then set setState with the spliced array.
deleteItem=(index)=>{
let todos= [...this.state.items]
todos.splice(index, 1)
this.setState({
items : todos
})
}
deleteItem = (index) => {
this.setState(({items}) => {
return {items: [...items.filter(todo => index !== todo.key)]};
})
}
First of all you're not setting the key anywhere when you are inserting in array. It is not at all recommended to use index as key in array. It should be unique.
const arrayItem = {
text: this.state.text,
id: uuid()
};
So I've added the uuid and compared with the id of the element.
codesandbox
uuid
// UNIQUE KEY GENERATOR
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export default uuidv4;
React component
import React, { Component } from "react";
import uuid from "./uuid";
import "./styles.css";
class App extends Component {
constructor(props) {
super(props);
this.state = { items: [], text: "" };
this.onChangeHandler = this.onChangeHandler.bind(this);
this.submitHandler = this.submitHandler.bind(this);
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
id: uuid()
};
this.setState((state) => ({
items: state.items.concat(arrayItem),
text: ""
}));
};
deleteItem = (key) => {
let todos = this.state.items.filter((todo) => key !== todo.id);
this.setState({
items: todos
});
};
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input
type="text"
onChange={this.onChangeHandler}
value={this.state.text}
/>
</form>
<ul>
{this.state.items.map((item) => {
return (
<li key={item.id}>
{item.text}
<p onClick={() => this.deleteItem(item.id)}> X </p>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;

toggle between sorted list and original list React.js

I'm trying to toggle between a descending ordered list then back to the original list that was rendered before sorting the list. The code is currently changing the original list upon clicking the sort button but does not return to the original list when clicked again. It stays in the sorted order.
class NonProfitContainer extends Component {
state = {
asc: true
}
toggleSort = () => {
let originalList = this.props.nonprofits.map(np => <NonprofitList key={np.id} nonprofit={np}/>)
let sortedList = this.props.nonprofits.sort((a, b) => b.name.localeCompare(a.name));
this.setState({
nonprofits: this.state.asc
? originalList
: sortedList,
asc: !this.state.asc,
});
};
render(){
const { asc } = this.state
return(
<div className="container">
<button onClick={() => this.toggleSort()}>{asc ? 'Sort Z-A' : 'Back'}</button>
<hr/>
{this.props.nonprofits.map(np => <NonprofitList key={np.id} nonprofit={np}/>)}
<hr/>
<h3>Add A New Nonprofit:</h3><br/>
<NonprofitForm />
</div>
)
}
}
const mapStateToProps = state => {
return {
nonprofits: state.nonprofitReducer.nonprofits,
}
}
export default connect(mapStateToProps,{getNonprofits})(NonProfitContainer)
Any advice would be appreciated! I'm pretty new to react.
You are sorting the props.nonprofits directly, you need to copy the values to another variable, sort it and if you want the original one, just copy that one.
You might need to use componentDidUpdate if you are getting values from redux to setState, just google it and you will get the idea. This code will cover your sorting issue.
https://codepen.io/ktdev/pen/abBwzvo
const NonprofitList = (props) => {
return <h1>{props.nonprofit.id}</h1>;
};
class Card extends React.Component {
static defaultProps = {
nonprofits: [
{ id: 1, name: "a" },
{ id: 2, name: "b" },
{ id: 3, name: "c" }
]
};
constructor(props) {
super(props);
this.state = {
asc: true,
nonprofits: props.nonprofits
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ darkMode: !this.state.darkMode });
}
toggleSort = () => {
let sortedList = [...this.props.nonprofits];
sortedList.sort((a, b) => b.name.localeCompare(a.name));
this.setState({
nonprofits: this.state.asc ? sortedList : this.props.nonprofits,
asc: !this.state.asc
});
};
render() {
const { asc } = this.state;
return (
<div className="container">
<button onClick={() => this.toggleSort()}>
{asc ? "Sort Z-A" : "Back"}
</button>
<hr />
{this.state.nonprofits.map((np) => (
<NonprofitList key={np.id} nonprofit={np} />
))}
<hr />
<h3>Add A New Nonprofit:</h3>
<br />
</div>
);
}
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title="Example Component" />, el);

React click on filtered List

I'm new to react. will really appreciate some help.
I have a list that I want to filter, and I want for each item in the list to click on it so it will open a page on the right side.
currently, the click is causing the list to disappear. I think I have an issue on the filtered list.
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
MyList: [
'A',
'B',
'C'
],
filter: "",
selectedItem: ""
}
this.handleClick = this.handleClick.bind(this);
}
handleFilter = (newFilter) => {
this.setState(() => ({
filter: newFilter
}));
}
handleClick(selectedItem) {
this.setState((prevstate) => ({
MyList: selectedItem
}));
}
render() {
const filteredList = this.state.MyList.filter(section =>
section.toLowerCase().includes(this.state.filter.toLowerCase()))
return (
<div>
{<Filter handleFilter={this.handleFilter} />}
{filteredList.map((listItem, i) =>
<p onClick={() => this.handleClick(i)}>{listItem}</p>)}
</div>
)
}
}
const Filter = (props) => (
<div>
<input name="filter" onChange={(e) => {
props.handleFilter(e.target.value);
}} />
</div>
);
ReactDOM.render(<MyApp />, document.getElementById('root'));
In your onClick function aren't you resetting your list from an initial array to a string? So on re-rendering you'll have a string as MyList. It should be:
handleClick(selectedItem) {
this.setState((prevstate) => ({
MyList: [selectedItem]
}));
}

React container with render logic?

I have a container, which renders 3 components:
1. list of items
2. new item modal
3. edit item modal
In order to control the whole container functions, I need to call the list of items with column list. Is it ok that all will be inside the container?
Is it ok to render modal within the container? (The modal contains the 2 and 3 components)
class Items extends React.Component {
constructor(props) {
super(props)
this.state = {
modal: false
}
this.columns = [
...
]
this.closeModal = this.closeModal.bind(this)
}
openModal(type, item) {
this.setState({
modal: {
type,
item: item && item.toJS()
}
})
}
closeModal() {
this.setState({modal: false})
}
renderModal() {
const {item, type} = this.state.modal;
return (
<Modal onClose={this.closeModal}>
{type == modalTypes.NEW_ITEM &&
<ItemForm onCancel={this.closeModal}
onSubmit={...}/>}
{type == modalTypes.REMOVE_ITEM &&
<ConfirmationDialog text="Are you sure you want to remove?"
onSubmit={...} onCancel={this.closeModal}/>}
{type == modalTypes.EDIT_ITEM &&
<ItemForm onCancel={this.closeModal}
onSubmit={...}/>}
</Modal>
)
}
render() {
const {visibleItems, display_type} = this.props;
return (
<div>
<div className="_header_container">
<Header title="Items"/>
<div className="actions">
<Search />
<DisplayToggle />
<Button size="sm" color="primary"
onClick={() => ...}
</div>
</div>
{display_type == displayType.GRID &&
<Grid items={visibleItems} columns={this.columns}/>}
{display_type == displayType.TILE &&
<TileView items={visibleItems} titleKey="name" linkKey="url"/>}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
remove: (item) => dispatch(remove(item)),
edit: (item, ...) => dispatch(edit(item, ...)),
create: (name, val) => dispatch(create(name, url)),
}
}
const mapStateToProps = (state) => {
return {
visibleItems: filterItems(state.items, state.search),
display_type: state.display_type
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Items)
Thanks

Showing two different components based on return value in react js

I have search function where on entering text it returns the object from an array(json data) and based on the condition (whether it as an object or not) I need to show two different components ie. the list with matched fields and "No matched results found" component.
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
searchTextData: '',
isSearchText: false,
isSearchOpen: false,
placeholderText:'Search Content',
filteredMockData: [],
dataArray: []
};
}
handleSearchChange = (event, newVal) => {
this.setState({ searchTextData: newVal })
if (newVal == '') {
this.setState({ clearsearch: true });
this.setState({
filteredMockData: []
});
this.props.onDisplayCloseIcon(true);
} else {
this.props.onDisplayCloseIcon(false);
searchData.searchResults.forEach((item, index, array) => {
this.state.dataArray.push(item);
});
this.setState({ filteredMockData: this.state.dataArray });
}
}
clearInput = () => {
this.setState({ searchTextData: '' })
}
isSearchText = () => {
this.setState({ isSearchText: !this.state.isSearchText });
}
onSearchClick = () => {
this.setState({ isSearchOpen: !this.state.isSearchOpen });
this.setState({ searchTextData: '' });
this.props.onDisplayCloseIcon(true);
}
renderSearchData = () => {
const SearchDatasRender = this.state.dataArray.map((key) => {
const SearchDataRender = key.matchedFields.pagetitle;
return (<ResultComponent results={ SearchDataRender } /> );
})
return SearchDatasRender;
}
renderUndefined = () => {
return ( <div className = "search_no_results" >
<p> No Recent Searches found. </p>
<p> You can search by word or phrase, glossary term, chapter or section.</p>
</div>
);
}
render() {
return ( <span>
<SearchIcon searchClick = { this.onSearchClick } />
{this.state.isSearchOpen &&
<div className = 'SearchinputBar' >
<input
placeholder={this.state.placeholderText}
className= 'SearchInputContent'
value = { this.state.searchTextData}
onChange = { this.handleSearchChange }
/>
</div>
}
{this.state.searchTextData !== '' && this.state.isSearchOpen &&
<span className='clearText'>
<ClearIcon className='clearIcon' clearClick = { this.clearInput }/>
</span>
}
{this.state.searchTextData !== '' && this.state.isSearchOpen &&
<div className="SearchContainerWrapper">
<div className = "arrow-up"> </div>
<div className = 'search_result_Container' >
<div className = "search_results_title" > <span> Chapters </span><hr></hr> </div>
<div className="search_show_text" >
<ul className ="SearchScrollbar">
{this.state.filteredMockData.length ? this.renderSearchData() : this.renderUndefined() }
</ul>
</div>
</div>
</div>}
</span>
);
}
}
Search.propTypes = {
intl: intlShape.isRequired,
onSearchClick: PropTypes.func,
isSearchBarOpen: PropTypes.func,
clearInput: PropTypes.func,
isSearchText: PropTypes.func
};
export default injectIntl(Search);
Search is my parent component and based on the matched values I need to show a resultComponent like
class ResultComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render(){
console.log(this.props.renderSearchData(),'Helloooo')
return(<p>{this.props.renderSearchData()}</p>)
}
}
ResultComponent.propTypes = {
results: PropTypes.string.isRequired
};
I'm getting an error "renderSearchData is not an function".I'm new to react and Hope someone can help.
The only prop passed to ResultComponent component is results
So in ResultComponent Component Replace
this.props.renderSearchData()
With
this.props.results

Resources