New component overwrite the previous one - javascript

import React, { Component } from 'react'
import Item from './components/item'
import './App.css'
class App extends Component {
state = { item: "", array: [], check: false }
setItem = (event) => {
this.setState({
item: event.target.value
})
}
add = () => {
let item = this.state.item;
if (item != "") {
let arr = []
arr.push(item)
this.setState({ item: "", array: arr, check: true })
}
console.log(this.state.array)
}
render() {
return ( < div > < input type = "text"
value = { this.state.item }
onChange = { this.setItem }
/ > < button onClick = { this.add } > Add < /button > {
this.state.check ? < div > {
this.state.array.map(item => < Item name = { item }
/>) } < /div >: null
} < /div > );
}
}
export default App
I actually wrote this code for building a item buying remainder.The problem is first item added successfully but after that i can'nt add more item.Every time i tried it overwrite the previously added item.

In your add function, if there is no item in the state, your are declaring arr to be a new (empty) array, and only pushing one item to it. Then, you use setState to overrwrite the current array with your new one (Which only contains one item)
To add to the array, you would need to first copy all of the items currently in it, then push onto them
add = () => {
let item = this.state.item;
if (item != '') {
this.setState(prevState => {
let arr = [...prevState.array]; // Shallow copy of the array currently stored in the state
arr.push(item);
return { item: '', array: arr, check: true };
});
}
console.log(this.state.array);
};

You are overwriting your array every time you add a new item.
Try this inside the add function:
before
let arr = []
after
let arr = this.state.array

Related

Array from Object is empty. Javascript and React

I am writing some react frontend application. I want to pass just header from table object to my function. I am using react-redux and redux-saga
In the reducer.js I am creating this object table, it has 2 arrays inside.
Code starts in render() and then calls Table which calls TableRows and TableHead
Later I pass table to TableHead as data variable (to avoid using as html table). But when I do data.header or data["header"] in TableHead method, the array I receive has 0 length and it is empty.
How can I get this array? I have to use map or some built-in functionality?
// Picture is created from this snippet
//AuthorsTable.js
const TableHead = ({data}) => {
console.log("Making header")
// var header = data.header
console.log(data)
console.log(data["header"])
console.log(data.header)
//reducer.js
...
case 'UPDATE_TABLE':
// console.log("Updating", action.author_obj.id)
var proto_obj = action.value;
// console.log("value:", obj)
var words = Object.keys(proto_obj).map(function (key) {
return {id: key, label: proto_obj[key]};
});
newState.table.header = [...newState.table.header, action.author_obj]
newState.table.content = [...newState.table.content, [words]]
return newState
...
//AuthorsTable.js
const TableHead = ({data}) => {
console.log("Making header")
// var header = data.header
console.log(data)
console.log(data["header"])
console.log(data.header)
var header = Object.keys(data.header).map(function (key) {
return {id: key, label: data[key]};
});
console.log(header)
const TableRows = ({data}) => {
return (<tbody><tr><td>content</td></tr></tbody>)
// for (let i = 0; i < 3; i++) {
// let children = []
// //Inner loop to create children
// for (let j = 0; j < 5; j++) {
// children.push(<td>{`Column ${j + 1}`}</td>)
// }
// return children
// }
}
const Table = ({data}) => {return(
<table>
<TableHead data={data} />
<TableRows data={data} />
</table>
)}
class AuthorsTable extends Component {
constructor(props) {
super(props);
}
render() {
const state = this.state;
// console.log(this.props.table)
return (
<Table data={this.props.table} />
// "Hello"
)
}
}
const mapStateToProps = state => {
return {
// authors: state.authors,
selectedItems: state.selectedItems,
table: state.table,
};
};
export default connect(
mapStateToProps,
)(AuthorsTable);
This was not working.
newState.table.header = [...newState.table.header, action.author_obj]
newState.table.content = [...newState.table.content, [words]]
Solution:
Creating new object, and then replacing it in state.
...
// reducer.js
// working code
const newState = { ... state };
var new_table = {
header: [...newState.table.header, action.author_obj],
content: [...newState.table.content, [words]],
}
newState.table = new_table
return newState
somebody knowing JS better can explain this further

Removing item from an array of items does not work

I am trying to remove an item from a list of items, but it does not seem to work. I have a page where I can add entries dynamically and items can be removed individually too. Adding seems to just work fine.
Sandbox: https://codesandbox.io/s/angry-heyrovsky-r7b4k
Code
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
values: []
};
}
onChange = event => {
this.setState({ value: event.currentTarget.value });
};
onAdd = () => {
this.setState({
value: "",
values: [...this.state.values, this.state.value]
});
};
onRemove = index => {
console.log(index);
let { values } = this.state;
let filteredIDs = values.splice(index, 1);
this.setState({
values: filteredIDs
});
};
render() {
let { values, value } = this.state;
return (
<>
<input
required
placeholder="xyz#example.com"
value={value}
onChange={this.onChange}
/>
<button onClick={this.onAdd}>Add</button>
<div>
<ul className="email-list-holder wd-minus-150">
{values.map((value, index) => (
<li key={index}>
{value}
<button
onClick={() => this.onRemove(index)}
style={{ cursor: "pointer" }}
>
Remove
</button>
</li>
))}
</ul>
</div>
</>
);
}
}
let filteredIDs = values.splice(index, 1); returns the removed item after it removes it from values
you'll want
onRemove = index => {
let { values } = this.state;
values.splice(index, 1);
this.setState({
values
});
tested and works on your codesandbox :p
Here is the working demo for you
https://codesandbox.io/s/purple-snow-kkudc
You have to change the below line.
let filteredIDs = values.splice(index, 1);
Use it instead of above one.
let filteredIDs = values.filter((x, i)=> i!==index);
Hope this will work for you.
I think you are using wrong javascript method when remove the item.
Splice method changes the contents of an array by removing or replacing existing elements and/or adding new elements
Slice method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included) where begin and end represent the index of items in that array. The original array will not be modified.
Replace
let filteredIDs = values.splice(index, 1);
With
let filteredIDs = values.slice(index, 1);
You are setting the removed part of the array instead of the correct one.
onRemove = index => {
console.log(index);
let { values } = this.state;
values.splice(index, 1);
this.setState({
values
});
};
This should work.
You set the removed items as the new values. This will fix it.
onRemove = index => {
console.log(index);
let { values } = this.state;
let filteredIDs = values.splice(index, 1);
this.setState({
values: values
});
};
splice returns the deleted elements and you are setting the removed elements. You can directly do:
values.splice(index, 1);
this.setState({
values,
})
You can also use uniqueId in order to give each new element a uniqueId this would help in filtering logic.
Here's how I may have structured the state and methods:
this.state = {
values: {
todo1: {
value: 'a'
},
todo2: {
value: 'b'
},
}
}
// Addition
this.setState({
values: {
...this.state.values,
uniqueId: {
value: 'New Value from input'
}
}
});
// Deletion
const stateValues = this.state.values;
delete stateValues[uniqueId];
this.setState({
values: stateValues,
});

Filtering an array by category name

I would like to filter an array of images based on their category property.
I am able to map and push the category property of all images into a new array and set the state to the new array.
However, I am stuck figuring out how to check for duplicates in the new array, and NOT push a new value if it's already exists.
interface Asset {
id: string
name: string
category: string
}
import * as React from "react"
interface MediaLibraryProps {
mediaLibrary: Asset[]
}
class MediaLibrary extends React.Component<MediaLibraryProps> {
state = {
categories: [],
}
categoryFilter = () => {
const categoryArray: any = []
this.props.mediaLibrary.filter(file => {
if (file.category !== categoryArray) {
categoryArray.push(file.category)
} else {
return
}
})
this.setState({
categories: categoryArray
})
console.log(this.state.categories)
}
render() {
const select = this.state.categories.map(category =>
<option key={category}>{category}</option>
)
return (
<div>
<select>
{ select }
</select>
<button onClick={this.categoryFilter}>LOG</button>
</div>
)
}
}
export default MediaLibrary
I'm expecting only unique names to be pushed to the categories array.
Actual results - everything is being pushed.
See Remove duplicate values from JS array
Example:
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
Quick answer for your question:
const categoryArray: any = []
this.props.mediaLibrary.filter(file => {
if (categoryArray.indexOf(file.category) < 0) {
categoryArray.push(file.category)
} else {
return
}
})
this.setState({
categories: categoryArray
})
console.log(this.state.categories)
Better approach:
The filter here is unnecessary. better approach is to use map.
const categoryArray: any = []
this.props.mediaLibrary.map(file => {
if (categoryArray.indexOf(file.category) < 0) {
categoryArray.push(file.category)
}
})
this.setState({
categories: categoryArray
})
console.log(this.state.categories)
It can be achieved with filter.As per question,
let filteredData = this.props.mediaLibrary.filter(file => file.category !== categoryArray);

React JS onClick set state of clicked index

How can I update isSelected to true for the item that the user clicks on?
Here is my handleSelect function so far:
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val, index) => {
//val.isSelected = index === true;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
This function is passed down correctly via props and is working, the issue I'm having is with the logic.
The current logic sets all items in the array to true which is incorrect. I only want to update / toggle the state of the item the user clicked.
I have an array of objects within state, here is what one of the objects looks like:
state = {
allGenres: [
{
id: 1,
genreTitle: 'Horror',
isSelected: false,
}
]
}
Here's how we can do it:
On each clicked Genre, we get its id.
After we have the id, then we toggle the selected genre isSelected flag.
Please follow updateGenres method, to check how we did it in an immutable way.
updateGenres(selectedId) {
const { allGenres } = this.state
this.setState({
// Here we make sure we don't mutate the state
allGenres: allGenres.map(genre => ({
...genre,
// Toggle the clicked one, and reset all others to be `false`.
isSelected: genre.id === selectedId
// If you want to keep the values of the rest genres, then the check should be:
// isSelected: (genre.id === selectedId) ? !genre.isSelected : genre.isSelected
}))
})
}
renderGenres() {
const { allGenres } = this.state
return allGenres.map(genre => <Gengre onClick={() => this.updateGenres(genre.id) })
}
render() {
return <div>{this.renderGenres()}</div>
}
The benefit of this approach is that you don't depend on the index of the allGenres and your toggle implementation will be decoupled to the presentation. Imagine if you change the genres sort (from ASC to DESC), then you have to change your toggle logic too.
Easiest way is that maintain allGenres and selectedGenre separately in state
So handleSelect will be like
handleSelect(id) {
this.setState({
selectedGenre: id
});
}
If the i passed to handleSelect is the index of the genre in the genres array then
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val, index) => {
val.isSelected = index === i;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
if the i refers to the id of the genre then
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val) => {
val.isSelected = val.id === i;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
I'll assume that you have some components being rendered, and that you want to click in these components... You could use bind and pass to the callback function the index of the array... for example:
class Component extends React.Component {
constructor() {
super();
this.state = {
allGenres: [{
id: 1,
genreTitle: 'Horror',
isSelected: false
}]
}
}
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val, index) => {
val.isSelected = index === i;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
render() {
const {allGenres} = this.state;
return (
<ul>
:D
{allGenres.map((genre, i) => (
<li onClick={this.handleSelect.bind(this, i)}>
{genre.genreTitle} {genre.isSelected && 'Is Selected'}
</li>
))}
</ul>
);
}
}
I'm using bind in the onClick callback from the li, this way I have access to the genre index in your callback.
There are some other ways of doing this though, since you have the id of the genre, you might want to use that instead of the index, but it's up to you to decided.

get next Next/Previous item in array using react

I'm new to react and programming in general, I have searched and only found solutions for js not react specific.
Having trouble displaying next or previous item in an array passed via props. When Next button is clicked I only see the same item in the array being returned not the next item, I understand previous will return null as displaying first item on load.
import React, { Component } from 'react'
import VideoPlayer from './Video'
import axios from 'axios'
export default class App extends Component {
constructor(props) {
super(props);
this._TogglePrev = this._TogglePrev.bind(this);
this._ToggleNext = this._ToggleNext.bind(this);
// app state
this.state = {
videos: [],
selectedVideo: null
}
}
componentDidMount() {
axios.get('http://localhost:5000/v1/video?id=287948764917205')
.then((result)=> {
var videos = result.data.payload
this.setState({
videos: videos,
selectedVideo: videos[0]
});
})
}
componentWillUnmount() {
this.serverRequest.abort()
}
// State transitions
_ToggleNext() {
console.log("something worked");
// take a copy of our state
const selectedVideo = this.state.selectedVideo;
// next video
var i = 0,
max = selectedVideo.length;
for (i; i < max; i += 1) {
if (selectedVideo[i]) {
return selectedVideo[i + 1];
}
}
//set our state
this.setState( selectedVideo );
console.log(selectedVideo)
}
_TogglePrev() {
console.log("something worked");
var current = this.state.selectedVideo;
var prev = current - 1;
if (prev < 0) {
prev = this.state.videos.length - 1;
}
// update our state
this.setState({ prev });
}
render() {
return (
<div className="App" style={{width: '100%', height: '100%'}}>
<div className="controls">
<button className="toggle toggle--prev" onClick={this._TogglePrev}>Prev</button>
<button className="toggle toggle--next" onClick={this._ToggleNext}>Next</button>
</div>
<VideoPlayer video={this.state.selectedVideo} />
</div>
)
}
}
The returned data
[
{ eventId: "287948764917205"
userName: "Jon Doe"
videoLink: "https://"https:s3.amazonaws.com/...""
userPhotoLink: "https://"https:s3.amazonaws.com/...""
},
{ eventId: "287948764917205"
userName: "Jane Thompson"
videoLink: "https://"https:s3.amazonaws.com/...""
userPhotoLink: "https://"https:s3.amazonaws.com/...""
}
]
Mistakes:
1. If you use return keyword inside for loop it will not only break the loop, it will return from that function also, so in these lines:
for (i; i < max; i += 1) {
if (selectedVideo[i]) {
return selectedVideo[i + 1];
}
}
this.setState( selectedVideo );
....
If if(selectedVideo[i]) will return true then it will break the loop and return from the function, so the lines after this for loop will never executes because of that return statement.
2. setState is a function and we need to pass an object (key-value pair, key will be the state variable names) in this, so you need to write it like this:
this.setState({ selectedVideo }); or this.setState({ selectedVideo: selectedVideo }); //both are same
Another way of writing the code by maintaining index:
1. Instead of maintaining selectedVideo in state variable maintain the index only, index of item of the array.
2. On click of next and prev button, increase or decrease the value of index and use that index to pass specific object of the state videos array to child component.
Like this:
import React, { Component } from 'react'
import VideoPlayer from './Video'
import axios from 'axios'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
videos: [],
selectedIndex: 0
}
this._TogglePrev = this._TogglePrev.bind(this);
this._ToggleNext = this._ToggleNext.bind(this);
}
componentDidMount() {
axios.get('http://localhost:5000/v1/video?id=287948764917205')
.then((result)=> {
var videos = result.data.payload
this.setState({
videos: videos,
selectedIndex: 0
});
})
}
componentWillUnmount() {
this.serverRequest.abort()
}
_ToggleNext() {
if(this.state.selectedIndex == this.state.videos.length - 1)
return;
this.setState(prevState => ({
selectedIndex: prevState.selectedIndex + 1
}))
}
_TogglePrev() {
if(this.state.selectedIndex == 0)
return;
this.setState(prevState => ({
selectedIndex: prevState.selectedIndex - 1
}))
}
render() {
let {selectedIndex, videos} = this.state;
return (
<div className="App" style={{width: '100%', height: '100%'}}>
<div className="controls">
<button className="toggle toggle--prev" onClick={this._TogglePrev}>Prev</button>
<button className="toggle toggle--next" onClick={this._ToggleNext}>Next</button>
</div>
<VideoPlayer video={videos[selectedIndex]} />
</div>
)
}
}
Use document.activeElement in order to get the currently focused element. Then, use nextElementSibling on order to get the next element then focus() just like thisdocument.activeElement.nextElementSibling.focus()
Full example:
export default function TextField() {
return (
<div
onKeyDown={(e:any)=>{
if (e.keyCode==13){
const active:any = document.activeElement
active.nextElementSibling.focus()
}
}}
>
<input/>
<input/>
<input/>
</div>
);
};
It's better to write in the constructor:
constructor(props) {
super(props);
this._TogglePrev.bind(this);
this._ToggleNext.bind(this);
// app state
this.state = {
videos: [],
selectedVideo: null,
selectedVideoIndex:0
}
}
and also change
_ToggleNext() {
console.log("something worked");
// take a copy of our state
const selectedVideo = this.state.selectedVideo;
// next video
var selectedVideoIndex = this.state.selectedVideoIndex; //i always starts with zero ????? you need also to save the index
max = selectedVideo.length;
for (selectedVideoIndex; selectedVideoIndex < max; selectedVideoIndex++) {
if (selectedVideo[selectedVideoIndex]) {
const retval = selectedVideo[selectedVideoIndex + 1];
this.setState( selectedVideoIndex+1 );
this.setState(retval );
return retval;
}
}
console.log(selectedVideo)
}

Categories