I have a Row Component that i am importing into my App.js component and apparently i but the delete function doesn't seem to be deleting the correct row. Even if i delete the second one, it deletes the last one first. So basically if i click delete on second row, it should delete the second one not the third etc.
My App.js or my root component looks like this: -
import React, {Component} from 'react';
import './App.css';
import Row from './Row.js'
class App extends Component {
state = {
rows: [
{value: 'row1', options:[1,2,3,4,5] },
{value: 'row2', options:[1,2,3,4,5] },
{value: 'row3', options:[ 1,2,3,4,5 ]}
]
}
updateValue = (e, idx) => {
const rows = [...this.state.rows];
rows[idx].value = e.target.value;
this.setState({
rows,
});
}
addRow = () => {
const rows = [...this.state.rows, {value:'', options: [1,2,3,4,5}];
this.setState({
rows,
});
}
deleteRow = (idx) => {
const copy = [...this.state.rows]
copy.splice(idx,1)
this.setState ({
rows:copy
})
}
render() {
return (
<div>
{
this.state.rows.map ( (row,idx) => {
return (
<Row
key = {idx}
value = { row.value }
onChange = { (e) => this.updateValue(e,idx) }
delete = { this.deleteRow.bind(this,idx) } />
)
})
}
<button onClick = {this.addRow}> Add </button>
</div>
)
}
}
export default App;
And my row component looks like this:-
const Row = ( props) => {
let options = props.options.map(opt => <option key={opt}>{opt}</option>);
return (
<div>
<input value= { props.value } onChange={ props.onChange }></input>
<select>
{options}
</select>
<button onClick = { props.delete} > Delete </button>
</div>
)
}
export default Row
I asked a similar question yesterday link but didn't realize at the time that the items weren't being deleted from the right position?
Try this
deleteRow = (item) => {
let filtered = this.state.rows.filter(row=>row.value!==item.value);
this.setState ({
rows: filtered
})
}
pass row to your delete function
<Row
key = {idx}
value = { row.value }
onChange = { (e) => this.updateValue(e,idx) }
delete = {()=>this.deleteRow(row) } />
)
if every row(object) is having unique id then use that id instead of value
like-
let filtered = this.state.rows.filter(row=>row.id!==item.id);
Related
I am looking for a way to hide a div once the button thats in it is clicked and continue to show all other div's.
I've tried using the setState method, however when setting it to false with onClick() all of my objects disappear.
class App extends React.PureComponent {
state: {
notHidden: false,
}
constructor(props: any) {
super(props);
this.state = {search: '', notHidden: true};
this.hideObject = this.hideObject.bind(this)
}
hideThisDiv() {
this.setState({notHidden: false})
}
async componentDidMount() {
this.setState({
objects: await api.getObjects()
});
}
render = (objects: Object[]) => {
return ({Object.map((object) =>
<div key={index} className='class'>
<button className='hide' type='button' onClick={() => hideThisDiv()}>Hide</button>
<p>object.text</p>
</div>}
render() {
const {objects} = this.state;
return (<main>
<h1>Objects List</h1>
<header>
<input type="search" onChange={(e) => this.onSearch(e.target.value)}/>
</header>
{objects ? this.render(objects) : null}
</main>)
}
);
The data is a data.json file filled with many of these objects in the array
{
"uuid": "dsfgkj24-sfg34-1r134ef"
"text": "Some Text"
}
Edit: Sorry for the badly asked question, I am new to react.
Not tested, just a blueprint... is it what you want to do?
And yes I didn't hide, I removed but again, just an idea on how you can hide button separately, by keeping in state which ones are concerned.
function MagicList() {
const [hidden, hiddenSet] = useState([]);
const items = [{ id:1, text:'hello'}, { id:2, text:'from'}, { id:3, text:'the other sided'}]
const hideMe = id => hiddenSet([...hidden, id]);
return {
items.filter( item => {
return hidden.indexOf(item.id) !== -1;
})
.map( item => (
<button key={item.id} onClick={hideMe.bind(this, item.id)}>{item.text}</button>
))
};
}
Edition
const hideMe = id => hiddenSet([...hidden, id]);
It is just a fancy way to write:
function hideMe(id) {
const newArray = hidden.concat(id);
hiddenSet(newArray);
}
I suggest using a Set, Map, or object, to track the element ids you want hidden upon click of button. This provides O(1) lookups for what needs to be hidden. Be sure to render your actual text and not a string literal, i.e. <p>{object.text}</p> versus <p>object.text</p>.
class MyComponent extends React.PureComponent {
state = {
hidden: {}, // <-- store ids to hide
objects: [],
search: "",
};
// Curried function to take id and return click handler function
hideThisDiv = id => () => {
this.setState(prevState => ({
hidden: {
...prevState.hidden, // <-- copy existing hidden state
[id]: id // <-- add new id
}
}));
}
...
render() {
const { hidden, objects } = this.state;
return (
<main>
...
{objects
.filter((el) => !hidden[el.uuid]) // <-- check id if not hidden
.map(({ uuid, text }) => (
<div key={uuid}>
<button
type="button"
onClick={this.hideThisDiv(uuid)} // <-- attach handler
>
Hide
</button>
<p>{text}</p>
</div>
))}
</main>
);
}
}
I want to click on an option in the menu. This options should then display all the items associated with that option in the child component. I know I am going wrong in two places. Firstly, I am going wrong in the onClick function. Secondly, I am not sure how to display all the items of ONLY the option (Eg.Brand, Holiday destination, Film) that is clicked in the child component. Any help would be greatly appreciated.
horizantalscroll.js
import React from 'react';
import ScrollMenu from 'react-horizontal-scrolling-menu';
import './hrizontalscroll.css';
import Items from './items';
// list of items
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return (
<div
className="menu-item"
>
{text}
</div>
);
};
onclick(){
this.onClick(name)}
}
// All items component
// Important! add unique key
export const Menu = (list) => list.map(el => {
const { name } = el;
return (
<MenuItem
text={name}
key={name}
onclick={this.onclick.name}
/>
);
});
const Arrow = ({ text, className }) => {
return (
<div
className={className}
>{text}</div>
);
};
const ArrowLeft = Arrow({ text: '<', className: 'arrow-prev' });
const ArrowRight = Arrow({ text: '>', className: 'arrow-next' });
class HorizantScroller extends React.Component {
state = {
selected: 0,
statelist: [
{name: "Brands",
items: ["1", "2", "3"]
},
{name: "Films",
items: ["f1", "f2", "f3"]
},
{name: "Holiday Destination",
items: ["f1", "f2", "f3"]
}
]
};
onSelect = key => {
this.setState({ selected: key });
}
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(this.state.statelist, selected);
const {statelist} = this.state;
return (
<div className="HorizantScroller">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
<items items={items}/>
</div>
);
}
}
export default HorizantScroller;
items.js
import React, { Component } from "react";
import HorizontalScroller from "horizontalscroll.js";
class Items extends React.Component{
render() {
return (
<div>
{this.statelist.items.map({items, name}) =>
name === this.statelist.name && <div>{items}</div>
}
</div>
);
}
}
export default Items;
The answer is in passing to your Items component the correct array it needs to show. You are already creating a state variable for what's selected. So it would be along the lines of:
<Items items={items[selected]}/>
I have a child component which i am looping over array to print out title and values. I have an event listener which renders a new row of title and values. I have a button in child component which i want do not want to be displayed by default but rendered only when i add new rows. So every 2 rows, there will be one button, for every 3, there will be 2 and so on.
This is my app.js file
import React, {Component} from 'react';
import './App.css';
import Child from './Child.js'
class App extends Component {
state = {
myArray: [
{ title: 'Hello', value: 'hello' }
]
}
addNewField = () => {
const myArray = [...this.state.myArray, { title: 'Hello', value: 'hello' }]
this.setState ({
myArray
})
}
render() {
return (
<div className = "App">
{
this.state.myArray.map ( (val,idx) => {
return (
<Child
key = {idx}
title = {val.title}
animal = { val.value }
/>
)
})
}
<button onClick = {this.addNewField}>Add Me</button>
</div>
)
}
}
export default App;
This is the setup for my Child Component:-
import React from 'react'
const Child = (props) => {
return (
<div>
<h3>{props.title}</h3>
<h4>{props.value}</h4>
<button>New Block!!</button>
</div>
)
}
export default Child
So basically the button in the Child component named new block should not be displayed by default but only after every click there after. Thank you.
Add a prop to the parent with the index of the map loop. Then add a flag so only children rendered after the first get the "New Block!!" button:
render() {
return (
<div className = "App">
{
this.state.myArray.map ( (val,idx) => {
return (
<Child
key = {idx}
title = {val.title}
animal = { val.value }
renderIndex = {idx}
/>
)
})
}
<button onClick = {this.addNewField}>Add Me</button>
</div>
)
}
}
import React from 'react'
const Child = (props) => {
return (
<div>
<h3>{props.title}</h3>
<h4>{props.value}</h4>
{props.renderIndex > 0 && <button>New Block!!</button>}
</div>
)
}
export default Child
I have a problem where I have to render 4 dropdowns, each with similar options(i just rendered 1 here). If I select an option in any one of the dropdowns, that option should not be available in the other three.
How should I update the selected_planets in the state? The below code updates the selected_planets from 1 select dropdown. But still that same option is available everywhere and I could not able to get 4 different options inside selected_planets array? How should I proceed?
Also, the response from API fetch is an array of objects, which I mapped through and update in planets Array. For demo purpose, let's consider, planets: [Neptune, Saturn, Mars, Earth, Venus, Jupiter]
import React, { Component } from 'react';
export default class Dashboard extends Component {
state = {
planets: [],
selected_planets: []
};
componentDidMount() {
fetch('url')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ planets: data });
});
}
handleSelect = event => {
this.setState({ selected_planets: [event.target.value] });
};
render() {
let select_Planets = this.state.planets.map(planet => {
return planet.name;
});
let options = select_Planets.map(planet => (
<option key={planet} value={planet}>
{planet}
</option>
));
return (
<select onChange={this.handleSelect}>
<option defaultChecked></option>
{options}
</select>
);
}
}
This can be achieved by producing a new set of options for each dropdown on render based off of what are the currently selected options, and what is the selected option for that dropdown.
First make sure each dropdown is binding to a property in your component's state and updating on change:
constructor() {
super();
this.state = {
planets: ["a", "b", "c", "d"],
inputs: {
d1: "",
d2: ""
}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange({ target }) {
this.setState({
...this.state,
inputs: {
...this.state.inputs,
[target.name]: target.value
}
});
}
<select
name="d1"
value={this.state.inputs.d1}
onChange={this.handleChange}>
Then you can obtain a list of the selected planets within the render method by converting the input object into an array using Object.values():
const selectedPlanets = Object.values(this.state.inputs);
Then create a new array for each of the dropdowns which will omit any planets which have already been selected unless it is selected by that particular dropdown itself:
const d1Options = this.state.planets.filter(
p => !selectedPlanets.find(sP => sP === p) || p === this.state.inputs.d1
);
const d2Options = this.state.planets.filter(
p => !selectedPlanets.find(sP => sP === p) || p === this.state.inputs.d2
);
<select
name="d1"
value={this.state.inputs.d1}
onChange={this.handleChange}>
<option></option>
{d1Options.map(o => (
<option key={o}>{o}</option>
))}
</select>
I've put together a working example here:
https://codepen.io/curtis__/pen/pozmOmx
You can solve this is multiple ways. Here is pseudo-code.
Create an object or array to hold the values of selected index. I will suggest to use useState API. Instead of setState.
class extends Component {
static state = {
dropdown1: "",
dropdown2: "",
dropdown3: "",
dropdown4: ""
}
constructor(props) {
super(props)
}
handleClick = (id, {target: {value}}) => {
this.setState({
[id]: value
})
}
render() {
<div>
<select onChange={this.handleClick.bind(null, "dropdown1")}>
</select>
<select onChange={this.handleClick.bind(null, "dropdown2")}>
</select>
<select onChange={this.handleClick.bind(null, "dropdown3")}>
</select>
<select onChange={this.handleClick.bind(null, "dropdown4")}>
</select>
</div>
}
}
You should replace this line
let select_Planets = this.state.planets.map(planet => {
return planet.name;
});
with
let select_Planets = this.state.planets.filter(planet => !this.state.selected_planets.includes(planet))
This will make sure that the only available options are those that have not been selected already. you can do it for all the other dropdowns.
you also replace the following lines
handleSelect = event => {
this.setState({ selected_planets: [event.target.value] });
};
with
handleSelect = event => {
this.setState({ selected_planets: [...this.state.selected_planets, event.target.value] });
};
You can manage all value in a Mother component, passing the selected option to all child components.
function DropdownBoxes({ url }) {
const [options, setOptions] = useState([]);
const [selected, setSelected] = useState({ a: 'ABC', b: 'CDE' });
useEffect(() => { // componentDidMount
fetch(url).then(setOptions);
}, [url]);
const onChange = (key, value) => {
setSelected(prev => ({ // this.setState
...prev,
[key]: value,
}));
}
const filterOptions = Object.values(selected); // ['ABC', 'CDE']
return (
<div>
<DropdownBox
options={options}
filterOptions={filterOptions}
value={selected['a']}
onChange={val => onChange('a', val)}
/>
<DropdownBox
options={options}
filterOptions={selected}
value={selected['b']}
onChange={val => onChange('b', val)}
/>
</div>
)
}
When you render the options, add a filter to show the option only if it is equal to the value, or it is not a subset of filterOptions. If you cannot/ do not want to chnage any code of dropdown box, you can add the filter to mother component when to passing options.
function DropdownBox({options, filterOptions, value, onChange}) {
...
const filter = (opt) => {
return opt.value === value || !filterOptions.includes(opt.value);
}
return (
<select>
{options.filter(filter).map(opt => (
<option value={opt.value} ...>{opt.label}</option>
))}
</select>
)
}
You can create a SelectDropDown view component and render it in the parent component. And the selected value for all dropdown is maintained by its parent component eg: selectedPlanets state.
// structure for selectedPlanets state.
type SelectedPlanets = {
[dropDownId: string]: string
};
Rusable SelectDropDown.js
import * as React from 'react';
type Props = {
valueField: string,
primaryKeyField: string, // field like id or value.
selectedValues: Array<string>, // values of the primaryField attribute that uniquely define the objects like id, or value
options: Array<Object>,
handleSelect: (event: SystheticEvent<>) => void
}
export default class SelectDropDown extends React.Component<Props> {
render() {
const {
options,
selectedValues,
primaryKeyField,
valueField,
handleSelect
} = this.props;
const optionsDom = options.map(option => {
if(!selectedValues.includes(option[primaryKeyField])){
return (
<option key={option[primaryKeyField]} value={option[valueField]}>
{planet}
</option>
);
}
});
return (
<select onChange={handleSelect}>
<option defaultChecked></option>
{optionsDom}
</select>
);
}
}
Sudo code Dashboard.js
import * as React from 'react';
import SelectDropDown from "./SelectDropDown"
export default class Dashboard extends React.Component {
state = {
planets: [],
selectedPlanets: {}
};
/*
Assumimg planets struct
[{
planetId: 1,
planet: "Earth"
}]
*/
componentDidMount() {
fetch('url')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ planets: data });
});
}
handleSelect = (dropDownId, event) => {
const { selectedPlanets } = this.state;
const selectedPlanetsCopy = {...selectedPlanets};
selectedPlanetsCopy[dropDownId] = event.target.value;
this.setState({ selectedPlanets: selectedPlanetsCopy });
};
getSelectedValues = (dropDownId) => {
const {selectedPlanets} = this.state;
const selectedValues = [];
Object.keys(selectedPlanets).forEach((selectedPlanetDropDownId) => {
if(dropDownId !== selectedPlanetDropDownId) {
selectedValues.push(selectedPlanets[selectedPlanetDropDownId]);
}
});
return selectedValues;
}
render() {
const { planets } = this.state;
return (
<SelectDropDown
valueField={"planet"}
primaryKeyField={"planetId"}
selectedValues={this.getSelectedValues("dropDown1")}
options={planets}
handleSelect={this.handleSelect.bind(this, "dropDown1")}
/>
<SelectDropDown
valueField={"planet"}
primaryKeyField={"planetId"}
selectedValues={this.getSelectedValues("dropDown2")}
options={planets}
handleSelect={this.handleSelect.bind(this, "dropDown2")}
/>
);
}
}
EDIT -
I am using this example here of the react-table select table (HOC)
Simply adding this code in the example code at the link mentioned above provides the results I am trying to achieve.
logSelection = () => {
console.log('selection:', this.state.selection);
this.setState({ selection: [] });
}
Background
I am using react-table in my React.js application. Specifically I am using the select table which provides a check box next to each row. In my case this allows the user to select multiple rows. Once the user submits their selections the data from the rows they selected is sent off for other use in my application.
After submission I am clearing the data that is being held in state. This is important in case they need to make another submission directly afterwords. After I clear the data from the state array holding each row that had previously been selected, the table still shows the previous rows selected even though the data is no longer being held in the state array.
Example Code
This is how I am clearing the array holding the selected arrays,
exportExcel() {
this.setState({ selection: [], selectAll: false });
}
This is all the relevant code in the class,
import React, {Component} from 'react';
import _ from 'lodash';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input } from 'reactstrap';
import ReactTable from 'react-table';
import checkboxHOC from 'react-table/lib/hoc/selectTable';
import 'react-table/react-table.css';
import Chance from 'chance';
import { connect } from 'react-redux';
import { fetchOdx } from '../../store/actions/Odx';
const CheckboxTable = checkboxHOC(ReactTable);
const chance = new Chance();
class TypeAHead extends Component {
constructor(props) {
super(props);
this.state = {
showRow: false,
showExcelForm: false,
modal: false,
selection: [],
selectAll: false,
state: {},
row: {},
column: {},
instance: {},
data: [],
};
this.showRow = this.showRow.bind(this);
this.showExcelForm = this.showExcelForm.bind(this);
this.toggleSelection = this.toggleSelection.bind(this);
this.toggleAll = this.toggleAll.bind(this);
this.isSelected = this.isSelected.bind(this);
this.exportExcel = this.exportExcel.bind(this);
this.setClientEmail = this.setClientEmail.bind(this);
this.props.fetchOdx();
}
componentWillReceiveProps(nextProps) {
if (nextProps.odxData) {
this.setState({
data: nextProps.odxData
});
}
}
showRow() {
this.setState({
showRow: !this.state.showRow
});
}
showExcelForm() {
this.setState({
clientEmail: '',
showExcelForm: !this.state.showExcelForm
});
}
toggleSelection(key, shift, row) {
let selection = [
...this.state.selection
];
const keyIndex = selection.indexOf(key);
if (keyIndex >= 0) {
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
selection.push(row);
}
this.setState({ selection });
}
toggleAll() {
const selectAll = this.state.selectAll ? false : true;
const selection = [];
if (selectAll) {
const wrappedInstance = this.checkboxTable.getWrappedInstance();
const currentRecords = wrappedInstance.getResolvedState().sortedData;
currentRecords.forEach((item) => {
selection.push(item._original._id);
});
}
this.setState({ selectAll, selection });
}
isSelected(key) {
this.state.selection.includes(key);
}
setClientEmail(event) {
this.setState({ clientEmail: event.target.value.toLowerCase() });
}
exportExcel(event) {
this.setState({ selection: [], selectAll: false });
this.showExcelForm();
}
render() {
const { toggleSelection, toggleAll, isSelected } = this;
const { selectAll, } = this.state;
const checkboxProps = {
toggleSelection,
toggleAll,
isSelected,
selectAll,
selectType: 'checkbox',
};
const columns = [{
Header: 'DebtCltRefNo',
accessor: 'DebtCltRefNo'
}, {
Header: 'DbtrDebtorType',
accessor: 'DbtrDebtorType',
}];
return (
<div>
<Button onClick={this.showExcelForm} color="success" size="lg" block>Export Excel</Button>
<CheckboxTable
data={this.state.data}
columns={columns}
className="-striped -highlight"
defaultPageSize={10}
ref={(r) => this.checkboxTable = r}
filterable
defaultFilterMethod={(filter, row) =>
String(row[filter.id]) === filter.value}
getTdProps={(state, row, column, instance) => ({
onClick: e => {
const r = row.original;
this.setState({ state, row: r, column, instance });
this.showRow();
}})}
{...checkboxProps}
/>
<div>
<Modal isOpen={this.state.showRow} toggle={this.showRow}>
<ModalHeader toggle={this.showRow}>{this.state.row.DebtCltRefNo}</ModalHeader>
<ModalBody>
DbtrDebtorType: {this.state.row.DbtrDebtorType}<br />
DebtType: {this.state.row.DebtType}<br />
</ModalBody>
<ModalFooter>
<Button color="danger" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
<div>
<Modal isOpen={this.state.showExcelForm} toggle={this.showExcelForm}>
<ModalHeader toggle={this.showExcelForm}>Grant Client Access</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="clientEmail">Client's Email Address</Label>
<Input value={this.state.clientEmail} onChange={this.setClientEmail} type="email" name="clientEmail" id="clientEmail" placeholder="Client's Email Address" />
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="danger" onClick={this.showExcelForm}>Cancel</Button>
<Button color="success" onClick={this.exportExcel}>Submit</Button>
</ModalFooter>
</Modal>
</div>
</div>
);
}
}
const mapStateToProps = state => {
if (state.Odx.odx) {
let data = state.Odx.odx;
data = _.forEach([...data], (o) => o._id = chance.guid());
return {
odxData: data
};
} else {
return {};
}
};
const mapDispatchToProps = dispatch => ({
fetchOdx: () => dispatch(fetchOdx()),
});
export default connect(mapStateToProps, mapDispatchToProps)(TypeAHead);
Question
How do I make the table update after my form is submitted, so that no rows have a check box checked? Or in other words, how can I get the table to coincide with the state array which shows the rows that have been selected? In my case, after I update the state array to be an empty array, the selected rows are still selected in the UI.
Try useTable({columns, data, defaultColumn, autoResetPage: !skipPageReset, updateMyData, autoResetSelectedRows: false}, ...)
Adding the autoResetSelectedRows: false option worked for me.