React key cant add to element - reactjs

Im new to React and feeling shy to ask this question,but I dont know why that when I try to bind key to li element,Icant find it after rendering DOM.
Which looks like:
<ul><br />
<li>I dont know why</li><br />
<li>cant find the key</li><br />
</ul><br />
in console.
And I got 'undefined' with e.target.getAttribute('key') in click event.
Here my code (a simple todolist):
class Write extends React.Component {
constructor(props){
super(props);
this.handle=this.handle.bind(this);
}
handle(){
const text=document.getElementById('todoIn').value;
this.props.onWriteDown(text);
}
render(){
return (
<div>
<input type="text" id="todoIn" />
<button onClick={this.handle}>confirm</button>
</div>
);
}
}
class Todolist extends React.Component {
constructor (props) {
super(props);
this.n=0;
this.state={list:[]};
this.todolist=[];
this.handle=this.handle.bind(this);
this.handle_del=this.handle_del.bind(this);
}
handle (m) {
this.todolist.push({thing:m,vid:this.n++});
this.setState({list:this.todolist});
}
handle_del (e) {
var d = this.todolist.forEach((v,i)=>{
if(v.vid==e.target.getAttribute('key')){
return i;
}
});
this.todolist.splice(d,1);
this.setState({list:this.todolist});
}
render(){
var that = this;
var todo=[];
//here I create React Element for each todo and bind some attribute BUT ONLY CLICK EVENT is ACCESSIBLE.I JUST WONDER...
this.state.list.forEach(function(v,i,a){
let temp=<li key={v.vid.toString()} onClick={that.handle_del} >{v.thing}</li>;
todo.push(temp);
});
return(
<div>
<Write onWriteDown={this.handle} />
<ul>
{todo}
</ul>
</div>
);
}
}
ReactDOM.render(
<Todolist />,
document.getElementById('root')
);
<div id="root"></div>

key is used internally so React knows which components have been added, changed or removed.
Also, keys don't get passed through.
For more information check ReactJS's doc.
https://facebook.github.io/react/docs/lists-and-keys.html#keys

As said in Hemerson's answer, you shouldn't be attempting to access the key attribute, it isn't passed through, and is only used as an internal reference to the object.
Instead, you can write your code as so:
let id = v.vid.toString();
let temp=<li key={id} onClick={() => that.handle_del(id)}>{v.thing}</li>;
handle_del (id) {
var d = this.todolist.forEach((v,i)=>{
if(v.vid==id){
return i;
}
});
this.todolist.splice(d,1);
this.setState({list:this.todolist});
}

Related

React mapping is returning first element from the array

Can someone please assist, I've been search for some time now and can't seem to solve the issue. I'm trying to get each element (playName, id) but I keep receiving this message:
react-dom.development.js:13231 Uncaught Error: Objects are not valid
as a React child (found: object with keys {playName, id}). If you
meant to render a collection of children, use an array instead.
I also keep getting the first elements (playName, id) from the bangerList but thats it, there's 8 I need to output. I am relatively new to react and would love some help.
Thanks
Luke
getCurrentUser(){
Spotify.getCurrentUser().then(res => {
// Need to get each playName element from the res
console.log(res)
res.map(bangerList =>{
console.log(bangerList)
this.setState({
bangerList: bangerList
})
})
});
}
handleNameChange(e){
this.props.onNameChange(e.target.value)
};
render(){
return (
<div className="Playlist">
<input onChange={this.handleNameChange}
defaultValue={"Playlist name"}
/>
<TrackList tracks={this.props.playlistTracks}
onRemove={this.props.onRemove}
isRemoval={true}
/>
<button className="Playlist-save" onClick={this.props.onSave}>SAVE TO SPOTIFY</button>
<MyBangers addit={this.getCurrentUser}
bangerListItems = {this.state.bangerList}
/>
</div>
class MyBangers extends React.Component{
constructor(props){
super(props)
this.getHandler = this.getHandler.bind(this);
}
// all this needs to go to app.js---------------
getHandler(){
}
// ---------------------------------------------
render(){
return(
<div className="myPlaylist">
<h2>My Bangers</h2>
<div>
{
}
</div>
<h3 className="hoverPlay">{this.props.bangerListItems}</h3>
<button onClick={this.props.addit}>click me</button>
</div>
)

Passing Function Down [React]

I'm trying to pass down a function called handleDeleteToDoItem from the parent ToDoContainer to the child ToDoListOfItems.
This is done with <ToDoListOfItems toDoItems={this.state.toDoItems} deleteToDoItem={this.handleDeleteToDoItem} />
However, I'm getting an error that the function is never received by the child class when I reference it with this.props.deleteToDoItem when I'm rendering ToDoItems inside of ToDoListOfItems
All of the other states which I've passed down from ToDoContainer are being recognized, except for deleteToDoItem and I'm at a loss of what I've done wrong here. My IDE (Webstorm) is telling me the variable is unresolved.
Is there a different way I should be passing down functions from parent components to child components?
Thanks,
import React, {Component} from 'react';
import './App.css';
class ToDoContainer extends Component {
constructor(props) {
super(props);
this.state = {
toDoItems: [],
toDoWhat: ""
};
...
this.handleDeleteToDoItem = this.handleDeleteToDoItem.bind(this);
}
handleDeleteToDoItem(uniqueID) {
let updatedItemList = this.state.toDoItems.filter(toDo => {
return toDo.uniqueID !== uniqueID;
});
// Concat updated item list to blank array
this.setState({
toDoItems: [].concat(updatedItemList)
})
}
render() {
return (
<div>
<div>
<div>
<ToDoListOfItems toDoItems={this.state.toDoItems} deleteToDoItem={this.handleDeleteToDoItem} />
</div>
</div>
{/* TODO Create a form with a submit button to add items to the todolist */}
<form action="">
<div>
{/* Capture the value of the form input */}
<input type="text" onChange={this.handleChangeToDoItem} value={this.state.toDoWhat}/>
</div>
<div>
<button onClick={this.handleAddToDoItem}>Add Item to List</button>
</div>
</form>
</div>
);
}
}
// This is the container for all the indivdual items in the to-do list
class ToDoListOfItems extends Component {
render() {
//TODO Put in styling for the list of items
// Use map() to iterate through each item in the to-do list, creating new elements in the list container
return (
<ul>
{this.props.toDoItems.map(toDo => (
<ToDoItem key={toDo.uniqueID}
id={toDo.uniqueID}
toDoWhat={toDo.toDoWhat}
completed={toDo.isDone}
onDelete={this.props.deleteToDoItem}/>
))}
</ul>
)
}
}
Its a key name mismatch issue, because in ToDoItem you are passing the function by key onDelete not by deleteToDoItem:
Here:
<ToDoItem key={toDo.uniqueID}
id={toDo.uniqueID}
toDoWhat={toDo.toDoWhat}
completed={toDo.isDone}
onDelete={this.props.deleteToDoItem} // here
/>
So inside ToDoItem component it will be available by this.props.onDelete.
Suggestion: To avoid the confusion use key deleteToDoItem at all the places.
Try making the deleteToDoItem prop in the container class equal to an arrow function calling the handleDeleteToDoItem with parameters:
... deleteToDoItem={uniqueID => this.handleDeleteToDoItem(uniqueID)}
Also, using an arrow function in this way makes it so you don't need to explicitly bind the handler function. Make it into an arrow function (above) and it is automatically bound.

Unable to access Children from json , react-redux

Hi, I'm able to access the contact name Ron and use filter in it. But, I'm unable to access the children contact Alex and use it in filter. I have attached my output screenshot. I need to print children Alex also in it and use filter. Thank you in Advance
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
let arr = [{"id":1,
"number":true,
"location":"2",
"time":11,
"code":1001,
"name":"Ron",
"children":[{
"id":141,
"number":true,
"location":1,
"time":1504969439000,
"code":null,
"name":"Alex"}]}]
const Tab = ({ value }) => <div> {value} </div>;
class App extends React.Component {
constructor() {
super();
this.state ={
search: ''
};
}
updateSearch(event){
this.setState({search: event.target.value.substr(0,20)})
}
render() {
let filteredContacts = arr.filter((item)=>{
return item.name.toLowerCase().indexOf(this.state.search)!==-1;
});
return(<div>
<h1> Contact List </h1>
<input type="text"
value={this.state.search}
placeholder="Search Here"
onChange={this.updateSearch.bind(this)} />
<ul>
{
filteredContacts.map((obj, i) =>
<Tab value={obj.name} key={i} />)
}
</ul>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('container'));
You filtering only goes through the first level of contacts in the array, so any contacts in .children will never be shown or be filtered against.
What you want to do is make an array that contains all the contacts, but have them at the same "bottom" level ([{name: 'Ron'}, {name: 'Alex'}]), so that we can filter on that array instead.
One way of doing this is to first .map over the original array and make sure we get both the "bottom" (parent) contact and the children in one array: (if the ... syntax is new to you, scroll to 'Spread in array literals' here)
const allContacts = originalContacts.map(parentContact => [parentContact, ...parentContact.children])
But that gets us an array of arrays: [[{name: 'Ron'}, {name: 'Alex'}]], which is hard to work with. So we want to make a new array where we have concatenated all the arrays in the allContacts array, into just one. Luckily, there is a way to call the .concat method that takes arguments in an array. It looks a bit odd, but we can do: (more about .apply)
const allContactsFlattened =
Array.prototype.concat.apply([], allContacts);
So now we have what we wanted: a "flat" (one level) array of all the contacts. Your filter method already knows how to handle that, so no further changes has to be made.
To see it in action I included your code with the changes applied (in a slightly different way) + some comments:
let originalContacts = [
{"name":"Ron (parent)", "children":[{"name":"Alex (child)"}, {"name":"Emma (child)"}]},
{"name":"Isa (parent)", "children":[{"name":"Sarah (child)"}, {"name":"Ahmad (child)"}]}
];
const Tab = ({ value }) => <div> {value} </div>;
class App extends React.Component {
constructor() {
super();
this.state ={
search: ''
};
}
updateSearch(event){
this.setState({search: event.target.value.substr(0,20)})
}
render() {
let contactsAndSubContacts =
// flatten the result
Array.prototype.concat.apply(
// transform from parent => [parent, child1, child2 etc..]
[], originalContacts.map(contact => [contact, ...contact.children])
);
let filteredContacts = contactsAndSubContacts.filter((item)=>{
// NOTE: you probably want to use toLowerCase()
// on this.state.search as well
return item.name.toLowerCase().indexOf(this.state.search.toLowerCase())!==-1;
});
return(<div>
<h1> Contact List </h1>
<input type="text"
value={this.state.search}
placeholder="Search Here"
onChange={this.updateSearch.bind(this)} />
<ul>
{
filteredContacts.map((obj, i) =>
<Tab value={obj.name} key={i} />)
}
</ul>
</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>

User react router inside of map

I would like to use react-router (must use 2.8.1) for rendering content inside a list (using map).
However, if I display {this.props.children} outside the .map, it renders one at time.
I need it to display inline/under the list entry.
How can I achieve this?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: x.movies
};
}
render() {
return (
<div>
<h2>Repos</h2>
{x.movies.map(movie =>
<span key={movie.id}>
{movie.name}
<NavLink to={"/repos/" + movie.id + "/next"}>React Router</NavLink>
</span>
)}
{this.props.children}
</div>
);
}
}
You are rendering the children inside the loop, which i believe is causing the extra Next Id:4 to be displayed between each entry.
Try the below, by rendering the children outside the loop.
{
x.movies.map(movie => (
<span>
<Result key={movie.id} result= {movie}/>
</span>
))
}
<span>{this.props.children}</span>

Force stop container subscribtion (Meteor+React+CreateContainer)

How can I manually stop the subscription and clean the collection on the client when you switch route via react-router?
The fact is that if I go to the page where there is a component subscribed to, such as "top 10 news" page, where published all the news, I see, as a container of news component performing a normal search the collection first finds objects with subscription last page.
export default createContainer((props)=>{
let {limitCount, userId} = props;
let query = (userId)?{'userId':userId}:{};
var handle = Meteor.subscribe('news_preview', query,limitCount);
var posts = Post.find(query, {sort:{createdAt:-1}}).fetch(); //Here, the container finds documents to which another component has been signed.
var needWait = !!handle.ready()&&!!posts;
return {
needWait:!needWait,
posts:posts
}
}, PostList)
After a moment, the container will complete its membership and will give us the relevant objects ...
How can I check that when linking the previous container has stopped its subscription and deleted objects?
In details. With the logic description
PostListContainer
This is a sample of NewsList component, with infinity scroll and subscribtion container. Is just detect scroll, and pass limitCount to child component.
export default class PostListContainer extends Component{
constructor(props){
super(props)
this.state={
limitCount:10,
}
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll, false);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll, false);
}
handleScroll(event) {
let documentHeight = $(document).height(); //jquery is bad idea, i know
let windowHeight = $(window).height();
let scrollPosition = $(window).scrollTop();
let isLoading = this.refs.PostList.data.needWait;
if(scrollPosition+windowHeight>=documentHeight && !isLoading){
this.uppendSkipCount()
}
}
uppendSkipCount(){
let limitCount = this.state.limitCount;
this.setState({
limitCount:limitCount+5
});
}
render(){
let userId = (this.props.userId)?this.props.userId:undefined;
return(
<div>
<PostList ref="PostList" limitCount={this.state.limitCount} userId={userId} />
</div>
)
}
}
PostList
This component get properties, subscribe and render child components.
export class PostList extends Component {
constructor(props){
super(props);
}
postList(){
return(
<Masonry>
{this.props.posts.map((post)=>(
<div className="col-xs-12 col-sm-6 col-md-6 col-lg-4" key={post._id}>
<PostPreview post={post}/>
</div>
))}
</Masonry>
)
}
render() {
let content = (this.props.posts)?this.postList():undefined;
let loading = (this.props.needWait)?<Loading />:undefined;
return(
<div>
{content}
{loading}
</div>
)
}
}
export default createContainer((props)=>{
let {limitCount, userId} = props;
let query = (userId)?{'userId':userId}:{};
var handle = Meteor.subscribe('news_preview', query,limitCount);
var posts = Post.find(query, {sort:{createdAt:-1}}).fetch();
var needWait = !!handle.ready()&&!!posts;
return {
needWait:!needWait,
posts:posts
}
}, PostList)
And it work goood. But if i will come on the page from page that contain for example BestNews component i will get Posts object from that in first iteration:(
export class BestNews extends Component {
constructor(props){
super(props);
}
componentWillUnmount(){
this.props.handle.stop(); / looks like it help
}
goToPage(post){
browserHistory.push(`/news/item/${post._id}`);
}
bestPosts(){
return bestPosts = this.props.posts.map((post)=>{
return(
<div key={post._id}>
<ListItem
onTouchTap={()=>this.goToPage(post)}
leftAvatar={<Avatar src={post.author().getAvatar().link()} />}
rightIcon ={<ActionChromeReaderMode />}
primaryText={post.title}
secondaryText={post.description}
/>
<Divider />
</div>
)
});
}
render() {
let content = (this.props.needWait)?<Loading />:this.bestPosts();
return (
<div className="box-margin">
<Paper zDepth={1}>
<div className="row middle-xs center-xs">
<div className="col-xs-12">
<h2 style={{fontWeight:'300'}}>Интересные новости</h2>
</div>
</div>
<div className="row start-xs">
<div className="col-xs-12">
<List>
<Divider />
{content}
</List>
</div>
</div>
</Paper>
</div>
)
}
}
export default createContainer((props)=>{
var handle = Meteor.subscribe('best_news');
var posts = Post.find({}, {sort:{likes:-1}, limit:5}).fetch();
var needWait = !handle.ready() && !posts;
return {
handle:handle,
needWait:needWait,
posts:posts
}
}, BestNews)
I found a solution.
It was obvious. But somehow it seems to me that there is a better one.
In this case, we need to use a child component of the container. With method componentWillUnmount() we can manually stop the subscription.
What is negative? The fact that we need to transfer the subscription object in a child component, and then manually stop the subscription. And to do it in each component, which can cause a similar problem.
The answer to the question - why can not we just wait for the completion of the subscription of the container, and then - to display the content. The answer is - it is possible, but as long as you have not implemented infinite scrolling logic. Otherwise, each time the user scrolls down the content will be lost. As long as the subscription is completed.
It's funny, but I always thought that the container so stops the subscription. I was wrong?
Solution with manualy stop subscribtion is not good. You understand why.
Take a look at the pretty good solution.
We need to protect themselves from the fact that when you visit a component we can get irrelevant data with other components. Yes type have the same data, but - with the other components. It can play a cruel joke with s. I think you understand why.
At the same time we can not put a cap "loading" every time a user scrolls down and loaded data. Because at the time of subscription, all data will disappear. Option with named subscriptions you too are not satisfied. For example, if you are using Astronomy ORM by Jagi.
So. We need simply to intercept getting values ​​from the container and once finished to record the fact of the first subscription in the state of the component.
export class PostList extends Component {
constructor(props){
super(props);
this.state={
firstSubscribtion:false
}
}
componentWillReceiveProps(nextProps){
if(!this.state.firstSubscribtion && nextProps.handleReady){
this.setState({
firstSubscribtion:true
});
}
}
postList(){
return(
<Masonry>
{this.props.posts.map((post)=>(
<div className="col-xs-12 col-sm-6 col-md-6 col-lg-4" key={post._id}>
<PostPreview post={post}/>
</div>
))}
</Masonry>
)
}
render() {
let content = (this.props.posts && this.state.firstSubscribtion)?this.postList():undefined;
let loading = (this.props.needWait)?<Loading />:undefined;
return(
<div>
{content}
{loading}
</div>
)
}
}
export default createContainer((props)=>{
let {limitCount, userId} = props;
let query = (userId)?{'userId':userId}:{};
var handle = Meteor.subscribe('news_preview', query,limitCount);
var posts = Post.find(query, {sort:{createdAt:-1}}).fetch();
var needWait = !!handle.ready()&&!!posts;
return {
handleReady:!!handle.ready(),
needWait:!needWait,
posts:posts
}
}, PostList)

Resources