When my code executes to remove an element, the last array element gets deleted, not the specific index I am referring to in the code.
Any assistance in reviewing this would be helpful.
constructor(props) {
super(props);
this.state = {
rows: []
}
this.addHandler = this.addHandler.bind(this);
this.removeHandler = this.removeHandler.bind(this);
}
addHandler(event){
const rows = this.state.rows.concat(<Component>);
this.setState({
rows
})
}
removeHandler(id){
const index = id;
this.setState({
row: this.state.row.filter( (x, i) => i !== index)
})
}
render () {
const rows = this.state.rows.map((Element, index) => {
return <Element key={index} id={index} index={index} func1=
{this.addHandler} func2={this.removeHandler} />
});
// func2 gets called by child
return (
<div className="rows">
<button onClick={this.addHandler} >+</button>
{rows}
</div>
);
}
Don't use index as a key for mapping out an array. Check out this package, easy to use and get the job done uuid
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'm trying to modify state and take the new state to render.
When I click and modified(added {isClicked: true} to array), console.log(this.state.listOfQuotes) inside onClicked function returns modified the full array of state(which I want to use)
but after render, console.log(this.state.listOfQuotes) returns only one clicked element and not even modified one...
Any help/hint much appreciated!
Here is my code
import React from "react";
export class Quotes extends React.Component {
constructor(props) {
super(props);
this.state = { listOfQuotes: [] };
this.vote = this.vote.bind(this);
this.onClicked = this.onClicked.bind(this);
}
componentDidMount() {
const url = "https://programming-quotes-api.herokuapp.com/quotes";
fetch(url)
.then(res => res.json())
.then(quote => {
this.setState({
listOfQuotes: quote
});
});
}
onClicked(id) {
const quotes = [...this.state.listOfQuotes];
const clickedQuote = quotes.findIndex(quote => quote.id === id);
console.log("onclicked", clickedQuote);
const newArray = { ...quotes[clickedQuote], isClicked: true };
console.log(newArray);
this.setState(prevState => ({
listOfQuotes: [
...prevState.listOfQuotes.splice(clickedQuote, 1, newArray)
]
}));
console.log(this.state.listOfQuotes); ----------> this one returns what i want
}
render() {
console.log(this.state.listOfQuotes); -----------> i want to have same result as above state
return (
<div className="quotes">
<div>
{this.state.listOfQuotes.map((quote, idx) => (
<div key={idx}>
<div onClick={() => this.onClicked(quote.id)}>
{!quote.isClicked ? (
<div className="before-clicked">{quote.en}</div>
) : (
<div className="after-clicked">{quote.en}</div>
)}
</div>
<div>By {quote.author}</div>
<div>Rating {quote.rating}</div>
<div className="vote">
<span>{quote.numberOfVotes}</span>
</div>
</div>
))}
</div>
</div>
);
}
}
There is a problem with your onClicked method.
It is not modifying the array correctly.
In my opinion, this is how it could have done.
onClicked(id) {
let quotes = [...this.state.listOfQuotes];
const clickedQuoteIndex = quotes.findIndex(quote => quote.id === id);
// Modify the object on the found index and assign true to "isClicked"
quotes[clickedQuoteIndex].isClicked = true;
// And then setState with the modified array
// Since setState is async, so the console shouldn't be called immediately
// but rather in the callback
this.setState({ listOfQuotes: quotes }, () => {
console.log(this.state.listOfQuotes);
});
}
I have a component that is a table.
Each row of this table is also component.
class FormulaBuilder extends Component {
constructor(props) {
super(props);
this.state = {
rows: [{}]
}
}
handleAddRow = () => {
const item = {};
this.setState({
rows: [...this.state.rows, item]
});
};
handleRemoveSpecificRow = (idx) => {
const rows = [...this.state.rows]
rows.splice(idx, 1)
this.setState({ rows })
}
render() {
return (
{
this.state.rows.map((item, idx) => {
return (
<React.Fragment key={idx}>
<ConcoctionRow
removeSpecificRow={(idx) =>this.handleRemoveSpecificRow(idx)}
id={idx} />
</React.Fragment>);
})
});
}
}
In the child component there is a button. When clicked, the event from the parent component is called:
class ConcoctionRow extends Component {
constructor(props) {
super(props);
}
handleRemoveSpecificRow = () => {
this.props.removeSpecificRow(this.props.id);
}
}
The properties passed the index of the array. But only the last line is always deleted not specific.
Where is my bad? P.S. I am new in JS.
A couple of things, you want to avoid using .splice() to update your arrays in components. Often times this actually ends up mutating your original state instead of creating a new one. A direct violation of React concepts.
Likewise lets try some stuff out on the console:
const arr = [1, 2, 3] <-- this is your state
const newArr = arr <-- you created a reference of your state. This does not actually create a new copy.
Now if you splice the newArr
newArr.splice(0, 1) <-- now newArr = [2, 3]
Well guess what, you also mutated your original state.
arr <-- is now also [2, 3]
A common misconception in JavaScript is that when you create a new variable that equals an existing variable, you expect that it actually creates a new copy.
let cat = {id: 1, name: "bunny"}
let myCat = cat
This is not actually the case, instead of explicitly creating a new copy, your new variable points to the same reference of the original object it is derived from. If I did something like:
myCat.age = 2 <-- Now myCat has a new property of age.
myCat <-- {id: 2, name: "bunny", age: 2}
BUT, because these two variables point to the same reference. You also mutate the original cat object as well
cat <-- {id: 2, name: "bunny", age: 2}
Use array.filter() instead to create a completely new array.
Here's an example with your code as well as a sandbox for reference: https://codesandbox.io/s/heuristic-nobel-6ece5
import React from "react";
import ConcoctionRow from "./ConcoctionRow";
class FormulaBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [{}, {}, {}]
};
}
handleAddRow = () => {
const item = {};
this.setState({
rows: [...this.state.rows, item]
});
};
handleRemoveSpecificRow = idx => {
const { rows } = this.state;
const updatedRows = rows.filter((row, index) => {
return index !== idx;
});
this.setState({
rows: updatedRows
});
};
render() {
return (
<div>
{this.state.rows.map((item, idx) => {
return (
<React.Fragment key={idx}>
<ConcoctionRow
removeSpecificRow={this.handleRemoveSpecificRow}
id={idx}
/>
</React.Fragment>
);
})}
</div>
);
}
}
export default FormulaBuilder;
I show the pattern I would use for this case. I recommend to use id instead of array index for items.
filter array function is immutable (it creates a new array, not mutates the previous one), so ok to use in set state. The functional form of setState is also a good stuff.
const Row = ({ onClick, children, id }) => (
<li>{children} <button onClick={() => onClick(id)}>Delete</button></li>
)
class App extends React.Component {
state = {
list: [
{id: 1, label: 'foo' },
{id: 2, label: 'bar' }
]
}
handleDelete = id => {
this.setState(prevState => ({
list: prevState.list.filter(row => (
row.id !== id
))
}))
}
render(){
const { list } = this.state;
return (
<ul>
{list.map(({ id, label }) => (
<Row id={id} onClick={this.handleDelete}>{label}</Row>
))}
</ul>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This is my simple react component. I set rooms firstly in state in componentWillReceiveProps and then on submit I do set rooms to data in state as well.
Also on submit I do an api call by passing single object from the rooms and when response come then I do slice that object from the data (but not from rooms) until the data length is equal to 0.
Now problem is when I do slice from the data then it slices the rooms elements as well.
class EditRoom extends Component {
constructor() {
super()
this.state = {
rooms: [],
data: []
}
}
componentWillMount() {
const { fetchRooms } = this.props
fetchRooms()
}
componentWillReceiveProps(np) {
const { rooms, setNick, setNickName } = np
if (this.props.rooms !== rooms) {
console.log('ppppppppppppp')
this.setState({ rooms })
}
if (setNick) {
const data = this.state.data
data.splice(0, 1)
this.setState({ data }, () => {
if (data.length === 0) {
console.log('pppp542545455453864')
} else {
const room = _.first(this.state.data)
setNickName(room)
}
})
}
}
handleInputChange(e, i) {
const { rooms } = this.state
rooms[i].nickname = e.target.value
this.setState({ rooms })
}
onSaveClick() {
const { setNickName } = this.props
this.setState({ data: this.state.rooms }, () => {
const room = _.first(this.state.data)
setNickName(room)
})
}
render() {
const { rooms } = this.state
return (
<div>
<main id="EditRoom">
{rooms &&
rooms.map((room, i) => {
return (
<div className="barcode-box" key={i} style={{ backgroundColor: this.getRandomColor(i) }}>
<div className="edit-room-name">
<input
type="text"
className="form-control"
style={{ color: '#ffffff' }}
name="cardNumber"
placeholder="Nickname"
value={_.get(room, 'nickname') || ''}
onChange={e => this.handleInputChange(e, i)}
/>
</div>
</div>
)
})}
</main>
</div>
)
}
}
What I am missing here?
Thank you !!!
You should not modify this.state directly, e.g. using array mutating methods like splice. Instead of this, make a copy from this.state.data sub-array, modify and pass it to setState().
Something like this:
const data = this.state.data.slice() // make a copy of data
data.splice(0, 1) // modify a copy
this.setState({ data }, ...) // pass to setState
[Update] Explanation of why changing one sub-array of state affects on another:
Arrays (as all objects) in JS are passed by reference. So if you do a simple assignment like arr2 = arr1, splice method will mutate the original array too. That's also true for nested arrays (objects) like in your case. data sub-array is stored with rooms together in state. So mutating data will affect rooms sub-array too.
I am trying to implement a filter and search function that would allow user to type in keyword and return result(array) and re-render the row
This is the event arrays that being passed in into the createDataSource function
The problem I am having now is my search function can't perform filter and will return the entire parent object although I specifically return the indexed object.
Here's what I got so far
class Search extends Component {
state = { isRefreshing: false, searchText: '' }
componentWillMount() {
this.createDataSource(this.props);
}
componentWillReceiveProps(nextProps) {
this.createDataSource(nextProps);
if (nextProps) {
this.setState({ isRefreshing: false })
}
}
createDataSource({ events }) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(events);
}
//return arrays of event from events
renderRow(event) {
return <EventItem event={event} />;
}
onRefresh = () => {
this.setState({ isRefreshing: true });
this.props.pullEventData()
}
setSearchText(event) {
let searchText = event.nativeEvent.text;
this.setState({ searchText })
var eventLength = this.props.events.length
var events = this.props.events
const filteredEvents = this.props.events.filter(search)
console.log(filteredEvents);
function search() {
for (var i = 0; i < eventLength; i++) {
if (events[i].title === searchText) {
console.log(events[i].title)
return events[i];
}
}
}
}
render() {
const { skeleton, centerEverything, container, listViewContainer, makeItTop,
textContainer, titleContainer, descContainer, title, desc, listContainer } = styles;
return(
<View style={[container, centerEverything]}>
<TextInput
style={styles.searchBar}
value={this.state.searchText}
onChange={this.setSearchText.bind(this)}
placeholder="Search" />
<ListView
contentContainerStyle={styles.listViewContainer}
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
refreshControl={
<RefreshControl
refreshing={this.state.isRefreshing}
onRefresh={this.onRefresh}
title="Loading data..."
progressBackgroundColor="#ffff00"
/>
}
/>
</View>
)
}
}
As you can see from the image above, my code requires me to type in the full query text to display the result. And it displays all the seven array objects? why's that?
The syntax of Array.prototype.filter is wrong... it should take a callback that will be the item being evaluated for filtering.. if you return true it will keep it.
function search(event) {
return ~event.title.indexOf(searchText)
}
You could even make the inline like..
const filteredEvents = this.props.events.filter(event => ~event.title.indexOf(searchText))
For understanding my use of ~, read The Great Mystery of the Tilde.
Since filter returns a new array, you should be able to clone your dataSource with it. If you didn't use filter, you would have to call events.slice() to return a new array. Otherwise, the ListView doesn't pickup the changes.