How to sort Array Data in ReactJs - javascript

class LifeCycleComps extends Component {
constructor(props) {
super(props);
this.state = {
data: 0,
names: [{
name: "sam"
},
{
name: "hammer"
},
{
name: "jellyfish"
}
]
};
//below is sortAlphabet function
sortAlphabet = () => {
this.setState({
names: this.state.names.sort()
});
};
//sortNames component
class SortNames extends Component {
render() {
return <span > {
this.props.names.name
} < /span>;
}
}
<button onClick={this.sortAlphabet}>sort</button>
<ul>
{this.state.names.map((item, i) => (
<SortNames key={i} names={item} /> ))}
</ul>
Above is my code. I am not sure what is the main problem. In the above code I want to get sorted names by onClick. But I am not getting any positive results from the above snippet. Please let me know folks what I did wrong?

You can not directly use sort function in array of object. For that you need to write a sort function or write a callback function which you can modify according your need. Here is working code(https://stackblitz.com/edit/react-31un7h) :
import React, { Component } from 'react';
import { render } from 'react-dom';
const SortNames = (props) => {
return (
<span >
{props.names.name}
</span>
)
}
class LifeCycleComps extends Component {
constructor(props) {
super(props);
this.state = {
data: 0,
names: [{
name: "sam"
},
{
name: "hammer"
},
{
name: "jellyfish"
}
]
};
}
compare = ( a, b ) => {
if ( a.name < b.name ){
return -1;
}
if ( a.name > b.name ){
return 1;
}
return 0;
}
//below is sortAlphabet function
sortAlphabet = () => {
this.setState({
names: this.state.names.sort(this.compare)
});
};
render(){
return (
<div>
<button onClick={this.sortAlphabet}>sort</button>
<ul>
{this.state.names.map((item, i) => (
<SortNames key={i} names={item} /> ))}
</ul>
</div>
);
}
}
//sortNames component
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
render() {
return (
<div>
<LifeCycleComps/>
</div>
);
}
}
render(<App />, document.getElementById('root'));

Here is sorted values
let arr = [{
name: "sam"
},
{
name: "hammer"
},
{
name: "jellyfish"
}]
function sortIt(x,y) {
if ( x.name < y.name ){
return -1;
}
if ( x.name > y.name ){
return 1;
}
return 0;
}
arr.sort(sortIt);
console.log(arr);
And here is in reactjs
sortIt(x,y) {
if ( x.name < y.name ){
return -1;
}
if ( x.name > y.name ){
return 1;
}
return 0;
}
sortAlphabet = () => {
this.state.names.sort(this.sortIt)
this.setState({
names: this.state.names.sort()
});
};
render() {
return (
<>
<button onClick={this.sortAlphabet}>sort</button>
<ul>
{this.state.names.map((item, i) => (
<li key={i} names={item}>{item.name}</li> ))}
</ul>
</>
);

Its to just adapt the javascript code provided in the comments as below.
compare = ( a, b ) => {
if ( a.name < b.name ){
return -1;
}
if ( a.name > b.name ){
return 1;
}
return 0;
}
//below is sortAlphabet function
sortAlphabet = () => {
let objs = [...this.state.names] //create a copy here as you will not want to directly mutate the state by calling sort.
this.setState({
names: objs.sort( compare );
});
};

I've made an StackBlitz in which you could see the solution. Hope this helps.

Related

React doesn't see mapped props

I have this message in browser:
TypeError: data.map is not a function
I am passing an array from another component here as props. What I am doing wrong?
Thank you in advance!
EDITED:
event-data.js
const months = ['January', 'February', 'March'];
const eventType = ['Party', 'Karaoke', 'Concert'];
const monthObject = [
{ id: 'sort-by-month' },
{ name: 'By month' },
{ values: months },
];
const eventObject = [
{ id: 'sort-by-category' },
{ name: 'By category' },
{ values: eventType },
];
const eventData = { monthObject, eventObject };
event-filter-bar.js
import eventData from '../../data/event-data';
class EventFilterBar extends React.Component {
render() {
return (
<FilterToolbar data={eventData} />
);
}
}
filter-toolbar.js
class FilterToolbar extends Component {
render() {
const { data } = this.props;
return (
<ButtonToolbar className="justify-content-center">
<DropdownMaker data={data} />
<DropdownWithDate />
<ResetButton />
</ButtonToolbar>
);
}
}
FilterToolbar.propTypes = {
data: PropTypes.array.isRequired,
};
dropdown-maker.js
class DropdownMaker extends Component {
render() {
const { data } = this.props;
const eventFilters = data.map((e) => (
<DropdownMenu
id={e.id}
name={e.name}
values={e.values}
key={e.id}
/>
));
return (
{ eventFilters }
);
}
}
DropdownMaker.propTypes = {
data: PropTypes.array.isRequired,
};
Check if the data is actually populated or not before map through it.
class DropdownMaker extends Component {
render() {
const { data } = this.props;
const eventFilters = (data && data.length > 0) && data.map((e) => (
<DropdownMenu
id={e.id}
name={e.name}
values={e.values}
key={e.id} //<-- don't forget to add a unique key prop while use loop
/>
));
return (
{ eventFilters }
);
}
}
DropdownMaker.propTypes = {
data: PropTypes.array.isRequired,
};
Feel free to comment if it's not working.

Update nested array values using map and index es6

I have a react form which has dynamic rows, but I'm stuck at line 76 (Demo: https://codesandbox.io/s/pw58j0vzoq), not sure what can I do to update the value in the row
class App extends React.Component {
state = {
acceptedValues: [
{
id: 1,
_arguments: ["Samsung", "xiaomi"]
},
{
id: 2,
_arguments: ["OR", "AND"]
}
]
};
handleChange = (name, index, argumentIndex) => e => {
const { acceptedValues } = this.state;
if (name === "_arguments") {
updatedState = acceptedValues.map((o, i) => {
if (i === index) {
return {
...o,
_arguments: o._arguments.map((o2, index2) => {
if (index2 === argumentIndex) {
//what to do here?
}
return o;
})
};
}
return o;
});
this.setState({
acceptedValues: updatedState
});
}
};
render() {
const { acceptedValues } = this.state;
return (
<div>
{acceptedValues.map(({ operator, _arguments }, index) => (
<div style={{ marginBottom: 20 }}>
<div>
{_arguments.map((val, argumentIndex) => (
<div>
<input
onChange={this.handleChange(
"_arguments",
index,
argumentIndex
)}
id="_arguments"
type="text"
value={val}
/>
<button onClick={this.removeArgument(index, argumentIndex)}>
-
</button>
</div>
))}
</div>
</div>
))}
</div>
);
}
}
I'm able to navigate until the correct array but stuck at how to update the value within the array, I've 2 indexs in my handleChange function.
There you are mapping an array into another, so you should just pick which string to return, like this:
_arguments: o._arguments.map(
(o2, index2) => {
if (index2 === argumentIndex) return e.target.value
return o2
}
)
Or in an even cleaner way:
_arguments: o._arguments.map(
(o2, index2) => index2 === argumentIndex ? e.target.value : o2
)

Set the setState to the searched books array to made it back to the blank array in case of invalid query

for a React project I have to setState the searched books array back to the blank array in case of invalid query, but I couldn't find a solution to my problem at the moment.
This is my code:
search_books = (val) => {
if (val.length !== 0 ) {
BooksAPI.search( val, 10 ).then((books) => {
if (books.length > 0 ) {
books = this.changeBookShelf(books)
this.setState(() => {
return {Books: books}
})
}
})
} else {
this.setState({Books: [], query: ''})
}
}
and this is the full code in case you'll need it:
import React, {Component} from 'react'
import {Link} from 'react-router-dom'
import * as BooksAPI from '../BooksAPI'
import Book from './Book'
import {PropTypes} from 'prop-types'
class BookSearch extends Component {
state = {
Books: [],
query: ''
}
static propTypes = {
onChange: PropTypes.func.isRequired,
myBooks: PropTypes.array.isRequired
}
handleChange = ( event ) => {
var value = event.target.value
this.setState(() => {
return {query: value}
})
this.search_books(value)
}
changeBookShelf = ( books ) => {
let all_Books = this.props.myBooks
for ( let book of books ) {
book.shelf = "none"
}
for ( let book of books ) {
for ( let b of all_Books ) {
if ( b.id === book.id ) {
book.shelf = b.shelf
}
}
}
return books
}
search_books = (val) => {
if (val.length !== 0 ) {
BooksAPI.search( val, 10 ).then((books) => {
if (books.length > 0 ) {
books = this.changeBookShelf(books)
this.setState(() => {
return {Books: books}
})
}
})
} else {
this.setState({Books: [], query: ''})
}
}
add_book = ( book, shelf ) => {
this.props.onChange( book, shelf )
}
render() {
return (
<div className="search-books">
<div className="search-books-bar">
<Link to='/' className="close-search">Close</Link>
<div className="search-books-input-wrapper">
<input type="text" placeholder="Search by title or author" value={this.state.query} onChange={this.handleChange}/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
{this.state.query.length > 0 && this.state.Books.map((book, index) => (<Book book={book} key={index} onUpdate={(shelf) => {
this.add_book( book, shelf )
}}/>))}
</ol>
</div>
</div>
)
}
}
export default BookSearch;
It's the last step but I can't figure out how to make the search launching an error if an invalid query is written in the search bar.
Looks like you're properly setting it to an empty array when there is no query, but forgetting to do so when there is a query string but no matching books. You just need to add an else statement in your .then function:
search_books = (val) => {
if (val.length !== 0 ) {
BooksAPI.search( val, 10 ).then((books) => {
if (books.length > 0 ) {
books = this.changeBookShelf(books)
this.setState(() => {
return {Books: books}
})
} else {
this.setState({Books: []})
}
})
} else {
this.setState({Books: [], query: ''})
}
You can add an error object to the state and use it to render a message:
state = {
Books: [],
query: '',
error: {
isError: false,
message: ''
}
}
Inside your search logic you can change the error object:
search_books = (val) => {
if (val.length !== 0 ) {
BooksAPI.search( val, 10 ).then((books) => {
if (books.length > 0 ) {
books = this.changeBookShelf(books)
this.setState(() => {
return {Books: books, error: {isError: false, message: ''}}
})
} else{
this.setState(() => {
return {Books: [], error: {isError:true, message:"No books"}}
})
}
})
} else {
this.setState({Books: [], query: '', {isError:true, message:"No books"}})
}
}
In your render method you can check the error object and display an error if needed:
render(){
// ...
error.isError && <div>{error.message}</div>
//...
}

reactjs components communicating

I have created two separate components and a parent component. I am trying to see how I can connect them so that I can have the dropdown for the children vanish when their checkbox is unchecked. I think I may have created this so the 2 components can't communicate, but I wanted to see if there was a way to get them to. Been trying different ways, but cannot seem to figure it out.
This is the parent component. It builds sections from some data and renders a checkbox treeview with the first (parent) checkbox having a dropdown. When the third option is selected in this dropdown, it renders in a dropdown for each child checkbox. I am trying to see if I can have the child dropdowns vanish when the checkbox is unchecked, but I can't seem to get the 2 components to communicate.
export default class CheckboxGroup extends PureComponent {
static propTypes = {
data: PropTypes.any.isRequired,
onChange: PropTypes.func.isRequired,
counter: PropTypes.number,
};
mapParents = (counter, child) => (
<li key={child.get('name')} className='field'>
<SegmentHeader style={segmentStyle} title={child.get('label')} icon={child.get('icon')}>
<div className='fields' style={zeroMargin}>
<div className='four wide field'>
<TreeCheckbox
label={`Grant ${child.get('label')} Permissions`}
counter={counter}
onChange={this.props.onChange}
/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</div>
<div className='twelve wide field'>
<GrantDropdown label={child.get('label')} childItems={child.get('items')}/>
</div>
</div>
</SegmentHeader>
</li>
)
mapDataArr = (counter) => (child) => (
(counter === 0 || counter === 1000) ?
this.mapParents(counter, child)
:
<li key={child.get('name')}>
<TreeCheckbox label={child.get('label')} onChange={this.props.onChange}/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</li>
)
buildTree = (dataArr, counter) => (
<ul key={counter} style={listStyle}>
{dataArr.map(this.mapDataArr(counter))}
</ul>
)
render() {
return (
<div className='tree-view'>
{this.buildTree(this.props.data, this.props.counter)}
</div>
);
}
}
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const pointer = { cursor: 'pointer' };
class TreeCheckbox extends PureComponent {
static propTypes = {
onChange: PropTypes.func,
label: PropTypes.string,
currentPerson: PropTypes.any,
};
componentDidMount() {
if (this.props.currentPerson.get('permissions').includes(this.props.label)) {
this.checkInput.checked = true;
this.changeInput(this.checkInput);
}
}
getLiParents = (el, parentSelector) => {
if (!parentSelector) parentSelector = document; // eslint-disable-line
const parents = [];
let parent = el.parentNode;
let o;
while (parent !== parentSelector) {
o = parent;
if (parent.tagName === 'LI') parents.push(o);
parent = o.parentNode;
}
return parents;
}
traverseDOMUpwards = (startingEl, steps) => {
let elem = startingEl;
for (let i = 0; i < steps; i++) {
elem = elem.parentNode;
}
return elem;
}
markIt = (nodeElem, checkIt, indeter) => {
const node = nodeElem;
const up = this.traverseDOMUpwards(node, 1);
node.checked = checkIt;
node.indeterminate = indeter;
this.props.onChange(up.children[1].innerText, checkIt);
}
changeInput = (event) => {
const e = event === this.checkInput ? event : event.target;
const selector = 'input[type="checkbox"]';
const querySelector = (el) => el.querySelectorAll(selector);
const container = this.traverseDOMUpwards(e, 2);
const markAllChildren = querySelector(container.parentNode);
const checked = e.tagName === 'LABEL' ? !markAllChildren[0].checked : e.checked;
const siblingsCheck = (element) => {
let onesNotRight = false;
const sibling = [].slice.call(element.parentNode.children);
sibling.filter(child => child !== element).forEach(elem => {
if (querySelector(elem)[0].checked !== querySelector(element)[0].checked) {
onesNotRight = true;
}
});
return !onesNotRight;
};
const checkRelatives = (ele) => {
let el = ele;
if (el.tagName === 'DIV') el = el.parentNode;
if (el.tagName !== 'LI') return;
const parentContainer = this.traverseDOMUpwards(el, 2);
if (siblingsCheck(el) && checked) {
this.markIt(querySelector(parentContainer)[0], true, false);
checkRelatives(parentContainer);
} else if (siblingsCheck(el) && !checked) {
const parent = this.traverseDOMUpwards(el, 2);
const indeter = parent.querySelectorAll(`${selector}:checked`).length > 0;
this.markIt(querySelector(parent)[0], false, indeter);
checkRelatives(parent);
} else {
for (const child of this.getLiParents(el)) {
this.markIt(querySelector(child)[0], false, true);
}
}
};
for (const children of markAllChildren) {
this.markIt(children, checked, false);
}
checkRelatives(container);
};
getRef = (input) => { this.checkInput = input; }
render() {
const { label } = this.props;
return (
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput} style={pointer}>
{label}
</label>
</div>
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
});
export default connect(mapStatetoProps)(TreeCheckbox);
class GrantDropdown extends AbstractSettingsComponent {
static propTypes = {
label: PropTypes.string,
currentPerson: PropTypes.any,
counter: PropTypes.number,
permissionOptions: PropTypes.any,
};
state = {
items: new List(),
}
componentDidMount() {
if (this.props.childItems) {
this.getAllChildLabels(this.props.childItems);
}
}
getAllChildLabels = (childItems) => {
let list = new List();
for (const item of childItems) {
list = list.push(item.get('label'));
if (item.get('items')) {
for (const childItem of item.get('items')) {
list = list.push(childItem.get('label'));
}
}
}
this.setState({ items: list });
}
handlePermissionChange = (label) => (e, { value }) => {
this.updatePerson(['locationsPermissionsMap', label], value);
}
mapItems = (val, i) => { // eslint-disable-line
const locationVal = this.props.currentPerson.getIn(['locationsPermissionsMap', val]);
return (
<div className={locationVal === 2 ? 'two fields' : 'field'} style={zeroMarginBottom} key={i}>
<OptionSelector
options={this.firstThreePermissionOpt()}
defaultValue={locationVal || 0}
onChange={this.handlePermissionChange(val)}
/>
{locationVal === 2 &&
<div className='field' style={zeroMarginBottom}>
<LocationMultiSelect name={val} {...this.props}/>
</div>
}
</div>
);
}
render() {
const { label, currentPerson } = this.props;
if (!currentPerson.get('permissions').includes(label)) {
return null;
}
const locationLabel = currentPerson.getIn(['locationsPermissionsMap', label]);
return (
<div className={ locationLabel === 2 ? 'two fields' : 'field'} style={zeroMarginBottom}>
<div className='field'>
<OptionSelector
options={this.getPermissionOptions()}
defaultValue={currentPerson.getIn(['locationsPermissionsMap', label]) || 0}
onChange={this.handlePermissionChange(label)}
/>
{locationLabel === 3 && this.state.items.map(this.mapItems)}
</div>
{locationLabel === 2 &&
<div className='field'>
<LocationMultiSelect name={label} {...this.props}/>
</div>
}
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
locations: state.get('locations'),
});
export default connect(mapStatetoProps)(GrantDropdown);
What you can do is set couple of props to send to child component to re-render them.
Example
export default class CheckBoxComponent extends React.Component {
changeInput() {
this.props.onCheckedChanged();
}
render() {
return(
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput.bind(this)} style={pointer}>
{label}
</label>
</div>
</div>
)
}
}
export default class DropDownComponent extends React.Component {
renderSelect() {
// here render your select and options
}
render() {
return(
<div>
{this.props.checkboxChecked === false ? this.renderSelect : null}
</div>
)
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxChecked: false
};
}
onCheckedChanged() {
this.setState({ checkboxChecked: !this.state.checkboxChecked });
}
render() {
return(
<div>
<CheckBoxComponent onCheckedChanged={this.onCheckedChanged.bind(this)} />
<DropDownComponent checkboxChecked={this.state.checkboxChecked} />
</div>
)
}
}
You can set the <input/> name attribute into a corresponding property in your state so the handler can get the name of the list / input via the event parameter and set the state respectively.
Then you can conditionally render the Dropdown according to the state.
Here is a small example of such behavior:
const lists = [
[
{ value: "0", text: "im 0" },
{ value: "1", text: "im 1" },
{ value: "2", text: "im 2" }
],
[
{ value: "a", text: "im a" },
{ value: "b", text: "im b" },
{ value: "c", text: "im c" }
]
];
const DropDown = ({ options }) => {
return (
<select>
{options.map(opt => <option value={opt.value}>{opt.text}</option>)}
</select>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showList0: false,
showList1: true
};
this.toggleCheck = this.toggleCheck.bind(this);
}
toggleCheck(e) {
const listName = e.target.name;
this.setState({ [listName]: !this.state[listName] });
}
render() {
return (
<div>
{lists.map((o, i) => {
const listName = `showList${i}`;
const shouldShow = this.state[listName];
return (
<div>
<input
type="checkbox"
name={listName}
checked={shouldShow}
onChange={this.toggleCheck}
/>
{shouldShow && <DropDown options={o} />}
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

React remove element from onclick

When a user deletes an item from their cart, I have the item displayed with a button to add it back to the cart. This works. Once the user adds the item back to their cart, I want the item in the display component to be removed. Here is my code for reference.
CART:
class Cart extends Component {
constructor(props) {
super(props);
this.state = {differences: [],};
}
componentWillReceiveProps(nextProps){
let thisProps = this.props.cart.items;
let theNextProps = nextProps.cart.items;
if (thisProps.map(i => i.sku).some(item => !theNextProps.map(i => i.sku).includes(item))) {
let diff = [thisProps.filter(item => !theNextProps.includes(item))];
this.setState({differences: this.state.differences.concat(diff)});
}
}
...
render = () => {
<CartAddBack data={this.state.differences} onAddToCart={this.props.addToCart} />
<CheckoutSection className='Checkout-cart-items' titleKey='checkout.items.title'>
{this.props.cart.items.map((item) => {
return (
<CheckoutItem item={item} key={item.sku} onRemoveProduct={this.props.removeFromCart} onUpdateQuantity={this.props.updateCartItem}/>
);
})}
</CheckoutSection>
}
}
CartAddBack:
class CartAddBack extends Component {
constructor() {
super();
this.state = {deleted: null};
this.onDelete = this.onDelete.bind(this);
}
onDelete(id){
console.log("THE SKU SHOULD BE HERE", id);
this.setState(id);
}
render() {
let {data} = this.props;
let theData = data.map(i => parseInt(i[0].sku));
let theStated = this.state.deleted;
return (
<div>
{data &&
<div className="CartAddBack">
<div className="CartAddBack-Wrapper">
<ul className="CartAddBack-Item-ul">
{theStated != null
? theData.filter(i => !theStated.includes(i)) &&
<CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
: data.map((item) => {
return <CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
})
}
</ul>
</div>
</div>
}
</div>
)
}
}
CartAddBackItem:
class CartAddBackItem extends Component {
constructor() {
super();
this.onClick = this.onClick.bind(this);
}
onDelete(){
this.props.onDelete({deleted: this.props.data.sku})
}
allowSubmit() {
this.setState({
allowSubmit: true,
});
}
onClick() {
if (this.props.data) {
if (this.props.data.quantity <= this.props.data.inventory_quantity) {
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: this.props.data.quantity}).then(success, failure);
}
else {
this.setState=({display: false});
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: 1}).then(success, failure);
}
}
}
render() {
let {data} = this.props;
return (
<li className="CartAddBackItem">
{data &&
<div className="CartAddBackItem-Wrapper">
<Button className="CartAddBackItem-button" onClick={this.onClick}><FormattedMessage id="cart.cartAddBack"/></Button>
<Link to={`product/${data.sku}`} className="CartAddBackItem-Link">
<p className="CartAddBackItem-title">{data.title}</p>
</Link>
</div>
}
</li>
)
}
}
I want CartAddBack to remove CartAddBackItem if the item was clicked in CartAddBackItem. Only thing I havent tried that I just thought about was to make a componentWillReceiveProps inside CartAddBack. But there has to be a better way. Issue I'm running into is my mapping items into CartAddBackItem. The gross looking {theStated != Null ? theData.filter(i =>... allows me to add items to the cart. It works if it was only data.map((item)=>... but I want to show my thinking. Any advice?

Categories

Resources