React: json index undefined passed with array iterator through onclick button - javascript

so for this component, it just shows a list of cards with some information passed through the props which is a JSON array set into events state. So, since it's a variable number of results each time I created an array of the number of times I want to the component to be rendered which works fine. Each card has its relevant information passed through
this.state.events[i]
Ok all that works fine EXCEPT one spot, which is the FlatButton onClick action.
<FlatButton label="Download ICS" onClick={() => console.log(this.state.events[i].location)} />
If I try access the state with i, then I will get a
TypeError: Cannot read property 'location' of undefined
or whatever property of the JSON I choose.
BUT, if i use an actual manual index like 5, 4, 3, etc then it works just fine. Thing is, I need it to be different for each button, otherwise other event cards have a button that does the wrong thing for that event. Keep in mind again that for all the other times I use i to access the JSON array, it works fine above such as in the CardText component.
Here is a visual:
I don't know, it might have something to do with component life cycle, but I don't know enough to know precisely.
Thanks
class SearchResultsList extends Component {
constructor(props) {
super(props);
this.state = {
events: [],
eventsLength: 0,
};
}
componentDidMount() {
var count = Object.keys(this.props.events).length;
this.setState({ events: this.props.events });
this.setState({ eventsLength: count });
};
render() {
var cardSearchHolder = [];
for(var i = 0; i < this.state.eventsLength; i++) {
cardSearchHolder.push(
(
<div key={i}>
<Card>
key={this.state.events[i]._id}
<CardHeader
title={this.state.events[i].event_name}
subtitle={this.convertDateTime(this.state.events[i].start_date, this.state.events[i].end_date)}
actAsExpander={true}
showExpandableButton={true}
/>
<CardText expandable={true}>
<div>
Category: {this.state.events[i].event_category}
</div>
<div>
Where: {this.state.events[i].location}
</div>
<div id="miniSpace"></div>
<div>
Description: {this.state.events[i].event_description}
</div>
</CardText>
<CardActions>
<FlatButton label="Download ICS" onClick={() => console.log(this.state.events[i].location)} />
</CardActions>
</Card>
<div id="space"></div>
</div>)
);
}
return(
<div>
<MuiThemeProvider>
<Card>
{cardSearchHolder}
</Card>
</MuiThemeProvider>
<div id="miniSpace"></div>
</div>
);
}
}

Related

on button return id to show specific json react js

I have a grid of boxes that fetched people's info from a JSON, each box have a button function that is from component 'CZButton', this button is included in "personlist" and it shows a pop up, I want to show the person's email inside the pop up , i am not sure how can i show a unique json item on each click, whatever i add in the pop up its shown on all the buttons, and what i want is to show specific details about the person once the button is clicked. I'm new to react and would appreciate the help. here is a sandbox snippet.
https://codesandbox.io/s/r5kz3jx3z4?fontsize=14&moduleview=1
A simple solution to this would be to extend the CZButton component so that it accepts a person property, by which the person data can then be rendered within the pop up dialog:
/* Adapted from your codesandbox sample */
class CZButton extends React.Component {
constructor(props) {
super(props);
this.state = { open: false };
}
toggle = () => {
let { toggle } = this.state;
this.setState({ open: !this.state.open });
};
render() {
const { open } = this.state;
return (
<div>
{" "}
<button onClick={this.toggle}>Show</button>
<Drawer
open={this.state.open}
onRequestClose={this.toggle}
onDrag={() => {}}
onOpen={() => {}}
allowClose={true}
modalElementClass="modal"
containerElementClass="my-shade"
parentElement={document.body}
direction="bottom" >
{/* This render the contents of the `person` prop's `email` field in dialog */}
<div>{this.props.person.email}</div>
{/* This renders the contents of `person` prop in dialog */}
<div>{JSON.stringify(this.props.person)}</div>
</Drawer>
</div>
);
}
}
Seeing that your CZButton now rendering the contents of the person prop, the change above would also require that you supply this data when rendering the CZButton in the PersonList component's render() method like so:
<div className="row">
{console.log(items)}
{items.map(item => (
<Person
className="person"
Key={item.id.name + item.name.first}
imgSrc={item.picture.large}
Title={item.name.title}
FName={item.name.first} >
{/* Pass the "person item" into our new person prop when rendering each CZButton */ }
<CZButton person={item} />
</Person>
))}
</div>
Here is a forked copy of your original code with the updates mentioned above for you to try out. Hope this helps!
In your PersonList component, as you map each of your items, you want to send the item's email as a prop to the CZButton, like this:
{items.map(item => (
<Person
className="person"
Key={item.id.name + item.name.first}
imgSrc={item.picture.large}
Title={item.name.title}
FName={item.name.first}
>
{" "}
<CZButton email={item.email} />
</Person>
))}
Now, each CZButton gets a prop called email. In, the render method of your CZButton, you will want the content of Drawer to look like this:
<Drawer ...>
<div>{this.props.email || "No email address for this person."}</div>
</Drawer>
You can try this out to see if it works for you.

React doesn't re-render on props change

I am kinda new to react and to the webitself.
this is my render function
render() {
const {repositories} = this.props
return (
<div className='mt4 bt b--black-20 boardingbox scrollarea-content' style={{overflow: 'scroll', height: '100vh'}}>
{
repositories.map((repo, index) => {
console.log(repo.name)
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
})
}
</div>
)
}
The repositories is changing the way I want, but for some reason the its not get re-rendered. I passing the repositiores property from the parent.
The first time I render it (click to the search button, get a response from the server, and set the repo array), its working fine. But at the 2nd search, when there is something in the array, its not working properly, and not re-render.
UPDATE:
The parent's render / onClick
render() {
const {repositories} = this.state
return (
<div className='w-third navpanel br b--black-20'>
<SearchBar onClick={this.onClick} onChange={this.onChange}/>
<RepoList repositories={repositories}/>
</div>
//<NewNote />
//<Tags />
//<NoteList />
);
}
onClick = (event) => {
const {searchTerm} = this.state
let endpoint = 'https://api.github.com/search/repositories?sort=stars&order=desc&q=' + searchTerm;
fetch(endpoint)
.then(blob => blob.json())
.then(response => {
if(response.items)
this.setState({ repositories: response.items });
})
}
UP-UPDATE:
Search Comp:
constructor({onClick, onChange}) {
super()
this.onClick = onClick
this.onChange = onChange
this.state = {
imageHover: false
}}
render() {
return (
<div className='flex items-center justify-between bb b--black-20'>
<div className='ma2 inputContainer w-100'>
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.onChange}/>
</div>
<div className='mr2'>
<div className='boardingbox pointer contain grow'>
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.onClick}/>
</div>
</div>
</div>
)}
first responde
second responde
and I am really ashamed that I could screw up like this.
So basicly the problem was:
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
So I was as stupid to use INDEX as a KEY so I could not add again the same key to the array.
Thanks anyway guys! :)
The root cause most probably is due to error in function binding.
In your SearchComponent you are using the "props" to create function bindings in the contructor. This can cause your SearchComponent to refer to wrong instance of the functions for onClick and onChange. Would suggest referring to the official documentation for more details.
you do not need to rebind the functions in your SearchComponent, you can just use the functions received in props.
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.props.onChange}/>
<!-- snipped other irrelevant code -->
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.props.onClick}/>
Why could be happening to cause this behavior
Remember, constructor is only called once the component instance is being constructed, once it has been created and remains alive, React lifecycles take over.
So, when you first render your screen, the component is created and since there is only 1 of everything, it kind of works.
When you run your first search: onChange/onClick callbacks modify the state of the parent component. Which then calls render on the parent component.
At this point, it is possible that your SearchComponent maybe holding on to the wrong instance of the call back methods, which would thus not set state on the parent and thus not force re-render.
Additional Notes on your constructor
Normally you shouldn't refer to props in your constructor, but if you need to, then you need to have it in the format below. Here are the relevant docs:
constructor(props) {
super(props);
// other logic
}

How do I iterate over an array with a map function, infinitely?

I am building a React JS application.
So, I want to print something over and over from an array, but it has only two elements. I am using a custom package called 'Typist' that enables me to give a 'Typing' kind of animation with whatever I type.
I am basically trying to type 'Hi', erase it and then type 'Ola' and then erase it and then start with 'Hi' again and keep repeating this pattern.
Here's what I have right now:
let greetings=["Hi","Ola"];
render() {
return(
<div className={"TypistExample-header"} >
<Typist className={"TypistExample"}>
<Typist.Delay ms={1000} />
{
greetings.map(i =>{
return <li><h1>{i}</h1>
{<Typist.Backspace count={12} delay={1000} />}
</li>
})
}
</Typist>
P.S I did find a way to do it a few times,still not infinite, by doing this:
let greetings=["Hi","Ola"];
var actualTyping= greetings.map(i =>{
return <li><h1>{i}</h1>
{<Typist.Backspace count={12} delay={1000} />}
</li>
});
var rows=[];
for(var i=0;i<10;i++){
rows.push(actualTyping)
}
return(
<div className={"TypistExample-header"} >
<Typist className={"TypistExample"}>
<Typist.Delay ms={1000} />
{rows}
</Typist>
</div>
);
You can use Typist's onTypingDone property to restart the animation. Pass the text array via state to Typist. When the typing is done, clear the state, which will remove the rendered Typist, then restore the original text, and it will be typed again (sandbox).
Note: setState is asynchronous, and it batches updates together or defers them to a later time. In this case we want to clear the text (set null), and only after the view is updated repopulate the text. To do so, we can use the 2nd param of setState, a callback that is fired only after the update (null) has been applied.
const greetings = ["Hi", "Ola"];
class ContType extends React.Component {
constructor(props) {
super(props);
this.state = {
text: props.text
};
}
onTypingDone = () => {
this.setState(
{
text: null
},
() =>
// run this callback after the state updates
this.setState({
text: this.props.text
})
);
};
render() {
const { text } = this.state;
return (
text && (
<Typist onTypingDone={this.onTypingDone}>
<Typist.Delay ms={1000} />
{text.map((i, index) => {
return (
<div key={index}>
<h1>{i}</h1>
{<Typist.Backspace count={12} delay={1000} />}
</div>
);
})}
</Typist>
)
);
}
}
render(<ContType text={greetings} />, document.getElementById("root"));
Better and simple solution would be creating a constant integer array of your choice and then mapping carried out using the value specified for integer.
const a = [1...10000]
let greetings = ["hi", "hello"]
render(){
return(
a.map( i => {
<h1>greeting[0] - greeting[1]</h1>
})
)
}
And always, keep in mind that infinite loop cause react engine to break down. Good practice is to specify an integer value for mapping for such cases.

React.js - re-drawing array after update of item

I have state like this:
this.state = {
items = []
}
State is loaded from REST service, which is working correctly.
However later , when user edit one of items in state, I use immutability helper to update state:
const newState = update(this.state, {
items: {
[index]: {$set: newItem}
}
});
this.setState(newState);
This will not be re-drawed (render method will pass , but no visible changes are made on page) and I know why:
Because I am creating array to render like this:
render(){
let itemsrender=[];
for (let i in this.state.items){
let line="";
if (i<this.state.items.length-1) line=<div className="row line"></div>;
atcrender.push(
<div key={this.state.item[i].id}>
<div className="row" >
<ITEM
item={this.state.items[i]}
onUpdate={this.onUpdate}
onDelete={this.onDelete}
/>
</div>
{line}
</div>
)
}
return(
<div>{itemsrender}</div>
);
}
If I can not edit render method of ITEM , is there any solution I can make so item will re-render on update ? Only one that come to my mind is to create wrapping Component.
I think that the issue is with the let itemsrender as this is what gets returned from the render method, this is all that will be rendered. You don't seem to update this anywhere in the loop so the return from the render method never changes.
You could also re-write this by mapping over the state and it might make it a bit easier to see whats going on.
render() {
return (
<div>
{this.state.items.map((item, index) => {
<div key={item.id}>
<div className="row" >
<ITEM
item={item}
onUpdate={this.onUpdate}
onDelete={this.onDelete}
/>
</div>
{index < this.state.items.length - 1 ? <div className="row line"></div> : ''}
</div>
})}
</div>
)
}
}
render(){
let itemsrender=[];
for (let i in this.state.items){
let line="";
if (i<this.state.items.length-1) line=<div className="row line"></div>;
atcrender.push(
<div key={this.state.item[i].id}>
<div className="row" >
<ITEM
item={this.state.items[i]}
onUpdate={this.onUpdate}
onDelete={this.onDelete}
/>
</div>
{line}
</div>
)
}
return(
<div>{itemsrender}</div>
);
}
Should atcrender on line 6, in fact be itemsrender? Or is there code elsewhere in this file that would reveal more about why you've done things this way?

ReactJS - array in this.state is updated correctly on delete but result isn't rendered until later state changes?

I've seen variations of this question posted but none match my problem. I am displaying an array of text fields and want to be able to delete one of them. When the delete button next to any text field is clicked, the last item in the array disappears. However, I've checked this.state and the correct item was deleted, just the wrong one was taken from the rendered array. When I click a button that hides the text fields and then un-hide, the array is fixed and shows the correct item deleted. Why is this happening? The state is updated with what I want removed, but it seems like it renders something other than the state. I've tried using this.forceUpload(); in the setState callback but that does not fix it.
I've included some relevant snippets from the code and can include more if needed.
export class Questions extends React.Component {
constructor(props) {
super(props);
this.state = {
...
choices: []
...
};
}
deleteChoice = (index) => {
let tempChoices = Object.assign([], this.state.choices);
tempChoices.splice(index, 1);
this.setState({choices: tempChoices});
};
renderChoices = () => {
return Array.from(this.state.choices).map((item, index) => {
return (
<li key={index}>
<TextField defaultValue={item.text} style={textFieldStyle} hintText="Enter possible response..."
onChange={(event) => { this.handleChoice(event, index); }}/>
<i className="fa fa-times" onClick={() => { this.deleteChoice(index); }}/>
</li>
);
});
};
render() {
let choices = (
<div>
<div className="choices">Choices</div>
<ul>
{this.renderChoices()}
<li>
<RaisedButton label="Add another" style={textFieldStyle} secondary
onClick={() => { this.addChoice(); }}/>
</li>
</ul>
</div>
);
return (
{choices}
);
}
}
Thanks for any help, this is wicked frustrating.
You need to use a key other than the array index in your renderChoices function. You can read more about why this is the case in React's docs:
https://facebook.github.io/react/docs/multiple-components.html#dynamic-children
When React reconciles the keyed children, it will ensure that any
child with key will be reordered (instead of clobbered) or destroyed
(instead of reused).
Consider using item.text as the key or some other identifier specific to that item.

Categories

Resources