Unexpected Behavior when Removing Items in React.js - reactjs

I've started to brush up on React after a long time away. Pretty much I have a "list" that stores companies interview processes. This is built by 2 react components. Is the list that aggregates each job.
When you go to "remove row" react registers the correct "row" to delete, (and by using a debugging simple case this happens) but it will not successfully update the inner component.
I've spent time researching this, and I've added a simple component called "Welcome." This helps me because I can use this to validate that I am removing the correct element, just the inner "jobrow" component is not updating correctly.
https://codepen.io/anon/pen/XwaWPj
class Jobs extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
jobs: props.data.items
//jobs: [{ id: "" }]
};
}
handleAddJob = () => {
this.setState({
jobs: this.state.jobs.concat([{ "company":"", "position": "", "next_steps": []}])
});
console.log(this.state);
};
handleRemoveJob = key => () => {
//var index = this.state.jobs.indexOf(items)
console.log(this.state.jobs.filter((item, j) => item.key !== key) )
this.setState({
//shareholders: this.state.shareholders.filter((s, sidx) => idx !== sidx)
//next_steps: this.state.next_steps.splice(idx, 1)
jobs: this.state.jobs.filter((item, j) => item.key !== key)
});
};
//<JobRow
// company={items.company}
// position={items.position}
// next_steps={items.next_steps}/>
render() {
return (
<div>
<h4>Jobs Applied</h4>
{this.state.jobs.map((items =>
<div>
<Welcome name={items.company} />
<JobRow
company={items.company}
position={items.position}
next_steps={items.next_steps}/>
<button
type="button"
onClick={this.handleRemoveJob(items.key)} //.bind(this)
className="small">
remove row
</button>
</div>
))
}
<button
type="button"
onClick={this.handleAddJob}
className="small">
Add New Job
</button>
</div>
)
};
}
// ===========
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// ===========
//https://stackoverflow.com/questions/50147840/how-to-format-and-display-json-data-using-array-map-in-reactjs
class JobRow extends React.Component {
constructor(props) {
super(props);
this.state = {
company: props.company,
position: props.position,
next_steps: props.next_steps,
};
}
handleNameChange = evt => {
this.setState({ name: evt.target.value });
};
handleAddField = () => {
this.setState({
//shareholders: this.state.shareholders.concat([{ name: "" }])
next_steps: this.state.next_steps.concat("")
});
};
handleRemoveField = idx => () => {
this.setState({
//shareholders: this.state.shareholders.filter((s, sidx) => idx !== sidx)
//next_steps: this.state.next_steps.splice(idx, 1)
next_steps: this.state.next_steps.filter((s, sidx) => idx !== sidx)
});
};
changeTextCompany(event){
this.setState(
//"{this.state.textValue : event.target.value}"
{company: event.target.value}
);
}
render() {
return (
<div>
<div class="flex-container">
<div class="inner_flex">
<span>
<input type="text" class="form-control" placeholder="Company" value={this.state.company} id="comapny_input" onChange={this.changeTextCompany}/>
</span>
<span>
<input type="text" class="form-control" placeholder="Position" value={this.state.position} oninput="selectJobType()" id="position_input"/>
</span>
<span>
<select id="position_type">
<option value="fulltime">Fulltime</option>
<option value="intern">Co-Op/Internship</option>
</select>
</span>
</div>
{this.state.next_steps.map((step, idx) => (
<span>
<button
type="button"
onClick={this.handleRemoveField(idx)}
className="small"
>
-
</button>
<input placeholder="Next State" value={step} />
</span>
))}
<button
type="button"
onClick={this.handleAddField}
className="small">
Next Stage
</button>
</div>
</div>
);
}
}
I would like for the correct row that is removed to be reflected in the text boxes.
I'd really appreciate your feedback.

You need to give each item in an array of elements a key (you should be getting a console warning about that). And the key should be a unique identifier, NOT the array index. In your code pen, instead of
{this.state.jobs.map((items =>
<div>
try
{this.state.jobs.map((items =>
<div key={items.key}>
Then it correctly deletes row you're selecting. And research why using array indices as a key (or why not using keys at all for arrays of components) causes problems in React.

Use getDerivedStateFromProps to update state in your JobsRow.
.flex-container {
display: flex;
background-color: #f1f1f1;
}
.flex-container > div {
background-color: #B6E3DC;
margin: 0px;
padding: 5px;
}
.flex-container > div > span {
display: inline-block;
padding: 2.5px;
}
input {display: block !important; padding: 0 !important; margin: 0 !important; border: 0 !important; width: 100% !important; border-radius: 0 !important; line-height: 1 !important;}
td {margin: 0 !important; padding: 0 !important;}
input {display: block !important; padding: 0 !important; margin: 0 !important; border: 0 !important; width: 100% !important; border-radius: 0 !important; line-height: 1 !important;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
var json = {"items":[
{"key":132, "company":"Google", "position": "SE Intern", "next_steps": ["Coding", "phone"]
},
{"key":133, "company":"FaceBook", "position": "DS Intern", "next_steps": ["Onsite", "Offer"]
},
{"key":134, "company":"twitter", "position": "architectre", "next_steps": ["coffeechat", "denail"]
},
{"key":135, "company":"oracle", "position": "sleeping", "next_steps": []
}
]}
class Jobs extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
jobs: this.props.data.items
//jobs: [{ id: "" }]
};
}
handleAddJob = () => {
this.setState({
jobs: this.state.jobs.concat([
{ company: '', position: '', next_steps: [] }
])
});
console.log(this.state);
};
handleRemoveJob = key => () => {
//var index = this.state.jobs.indexOf(items)
//console.log(this.state.jobs.filter((item, j) => item.key !== key));
this.setState({
//shareholders: this.state.shareholders.filter((s, sidx) => idx !== sidx)
//next_steps: this.state.next_steps.splice(idx, 1)
jobs: this.state.jobs.filter((item, j) => item.key !== key)
});
};
//<JobRow
// company={items.company}
// position={items.position}
// next_steps={items.next_steps}/>
render() {
return (
<div>
<h4>Jobs Applied</h4>
{this.state.jobs.map(items => (
<div>
<Welcome name={items.company} />
<JobRow
company={items.company}
position={items.position}
next_steps={items.next_steps}
/>
<button
type="button"
onClick={this.handleRemoveJob(items.key)} //.bind(this)
className="small"
>
remove row
</button>
</div>
))}
<button type="button" onClick={this.handleAddJob} className="small">
Add New Job
</button>
</div>
);
}
}
// ===========
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// ===========
//https://stackoverflow.com/questions/50147840/how-to-format-and-display-json-data-using-array-map-in-reactjs
class JobRow extends React.Component {
constructor(props) {
super(props);
this.state = {
company: props.company,
position: props.position,
next_steps: props.next_steps
};
}
static getDerivedStateFromProps(props, state) {
// compare props with state data
// if they are not equal return props
// or return null
// more info here https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
return props;
}
handleNameChange = evt => {
this.setState({ name: evt.target.value });
};
handleAddField = () => {
this.setState({
//shareholders: this.state.shareholders.concat([{ name: "" }])
next_steps: this.state.next_steps.concat('')
});
};
handleRemoveField = idx => () => {
this.setState({
//shareholders: this.state.shareholders.filter((s, sidx) => idx !== sidx)
//next_steps: this.state.next_steps.splice(idx, 1)
next_steps: this.state.next_steps.filter((s, sidx) => idx !== sidx)
});
};
changeTextCompany(event) {
this.setState(
//"{this.state.textValue : event.target.value}"
{ company: event.target.value }
);
}
render() {
return (
<div>
<div class="flex-container">
<div class="inner_flex">
<span>
<input
type="text"
class="form-control"
placeholder="Company"
value={this.state.company}
id="comapny_input"
onChange={this.changeTextCompany}
/>
</span>
<span>
<input
type="text"
class="form-control"
placeholder="Position"
value={this.state.position}
oninput="selectJobType()"
id="position_input"
/>
</span>
<span>
<select id="position_type">
<option value="fulltime">Fulltime</option>
<option value="intern">Co-Op/Internship</option>
</select>
</span>
</div>
{this.state.next_steps.map((step, idx) => (
<span>
<button
type="button"
onClick={this.handleRemoveField(idx)}
className="small"
>
-
</button>
<input placeholder="Next State" value={step} />
</span>
))}
<button type="button" onClick={this.handleAddField} className="small">
Next Stage
</button>
</div>
</div>
);
}
}
ReactDOM.render(<Jobs data={json}/>, document.getElementById('root'));
</script>
Another option is to access the props directly in your JobsRow instead of saving them in state.
<span>
<input
...
value={this.props.company}
...
/>
</span>
<span>
<input
...
value={this.props.position}
...
/>
</span>

Related

Unable to pass props from parent to child and save it in state of child component

I'm trying to develop a website for fetching GitHub data, but I'm having problem in updating the component that shows data Formdata component. It doesn't seem to be updating form some reasons.
App:
export default class App extends Component {
constructor(props){
super(props);
this.state = {
uname:'',
udata:'',
};
this.handleInput = this.handleInput.bind(this);
this.getUser = this.getUser.bind(this);
}
getUser(){
fetch(`https://api.github.com/users/${this.state.uname}`)
.then(response => response.json())
.then(data => this.setState({udata:data}))
.catch(error => console.error(error));
}
handleInput(event){
this.setState({
uname:event.target.value
});
}
render() {
return (
<div>
<Header></Header>
<Form handleInput={this.handleInput} uname={this.state.uname} getUser={this.getUser}></Form>
<Formdata udata={this.state.udata}></Formdata>
</div>
)
}
}
Form:
export default function Form(props) {
const {getUser, handleInput, uname} = props;
return (
<div className="form">
<input className="textbar" placeholder="Search for username" value={uname} onChange={handleInput} name="uname"></input>
<button className="button" onClick={getUser} >Search</button>
</div>
)
}
Formdata:
export default class Formdata extends Component {
constructor(props){
super(props);
this.state = {
follower:'',
following:'',
public_repos:'',
visit_page:'',
avatar:''
}
this.updateUser = this.updateUser.bind(this);
};
componentDidMount(props){
this.updateUser();
}
updateUser(){
this.setState({follower:this.props.udata.followers});
this.setState({following:this.props.udata.following});
this.setState({public_repos:this.props.udata.public_repos});
this.setState({visit_page:this.props.udata.url});
this.setState({avatar:this.props.udata.avatar_url});
console.log(this.props.udata);
}
render() {
return (
<div>
<img className="imge" src= {this.state.avatar} alt=" "></img>
<div className="details">
<div className="compon">Followers: {this.state.followers}</div>
<div className="compon">Following: {this.state.following}</div>
<div className="compon">public repos" {this.state.public_repos}</div>
</div>
<div className="urls">Page:{this.state.visit_page}</div>
</div>
)
}
}
I can't figure out how to update component Formdata on clicking search button in Form component.
Full Working App: StackBlitz
import React, { Component, useEffect } from "react";
import "./style.css";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
uname: "",
udata: ""
};
this.handleInput = this.handleInput.bind(this);
this.getUser = this.getUser.bind(this);
}
getUser() {
fetch(`https://api.github.com/users/${this.state.uname}`)
.then(response => response.json())
.then(data =>
this.setState({ udata: data }, () => {
console.log(this.state.udata);
})
)
.catch(error => console.error(error));
}
handleInput(event) {
this.setState(
{
uname: event.target.value
},
() => {
console.log(this.state.uname);
}
);
}
render() {
return (
<div>
<Form
handleInput={this.handleInput}
uname={this.state.uname}
getUser={this.getUser}
/>
<Formdata udata={this.state.udata} />
</div>
);
}
}
const Form = props => {
const { getUser, handleInput, uname } = props;
return (
<div className="form">
<input
className="textbar"
placeholder="Search for username"
value={uname}
onChange={handleInput}
name="uname"
/>
<button className="button" onClick={getUser}>
Search
</button>
</div>
);
};
const Formdata = ({ udata }) => {
useEffect(() => {
console.log(JSON.stringify(udata.login));
}, [udata]);
return (
<div style={styles.card}>
{udata.login ? (
<div style={styles.cardImg}>
<div>
<img
style={styles.img}
className="imge"
src={udata?.avatar_url}
alt=" "
/>
</div>
<div className="details">
<div className="compon">Followers: {udata?.followers}</div>
<div className="compon">Following: {udata?.following}</div>
<div className="compon">Public repos: {udata?.public_repos}</div>
<div className="urls">Page: {udata?.url}</div>
</div>
</div>
) : (
<div>
<p>No Data Available</p>
</div>
)}
</div>
);
};
const styles = {
card: {
display: "flex",
flex: 1,
backgroundColor: "rgba(21,21,21,0.2)",
padding: 10,
marginTop: 10,
borderRadius: 5
},
cardImg: {
display: "flex",
flex: 1,
flexDirection: "row",
flexWrap: "wrap",
overflow: "hidden",
textOverflow: "ellipsis",
color: "rgba(0,0,0,0.7)"
},
img: {
marginRight: 10,
width: 100,
height: 100,
borderRadius: 10,
overflow: "hidden"
}
};
Do not copy props into state, use the props directly in your JSX:
div>
<img className="imge" src= {this.props.udata.avatar} alt=" "></img>
<div className="details">
<div className="compon">Followers: {this.props.udata.followers}</div>
<div className="compon">Following: {this.props.udata.following}</div>
<div className="compon">public repos" {this.props.udata.public_repos}</div>
</div>
<div className="urls">Page:{this.props.udata.visit_page}</div>
</div>
If you copy props into state, you are creating redundant copy of props and it is difficult to keep props and state in sync. And it is a React anti-pattern.
Just make sure this.props.udata is not undefined, it is ok if it is empty object {}. If it is undefined, put a check / conditional rendering.
anti-pattern-unconditionally-copying-props-to-state
Formdata.updateUser() isn't being called at any point. You probably just need to call it in componentDidMount():
export default class Formdata extends Component {
...
componentDidMount(props){
this.updateUser();
}
updateUser(){
this.setState({follower:this.props.udata.followers});
this.setState({following:this.props.udata.following});
this.setState({public_repos:this.props.udata.public_repos});
this.setState({visit_page:this.props.udata.url});
this.setState({avatar:this.props.udata.avatar_url});
console.log(this.props.udata);
}
...
}

React Current Image in Image Gallery

Hello i have a litle problem with react-image-gallery.
In ImageGallery component i pass startIndex value like this.state.currentImage and this.state.currentImage depends on number photo with we are going to click.
When we click on photo for example number 4 this.state.currentImage is chaining on number 4 and its correct for me but in <imageGallery/> component startIndex doesn't work like i should. My modal always start on first image index[0].
import React, { Component } from "react";
import { Modal, ModalClose, ModalBody } from "react-modal-bootstrap";
import ImageGallery from "react-image-gallery";
import "./index.css";
export default class Images extends Component {
constructor(props) {
super(props);
var data = { title: "photos", images: [], ...props.data };
this.state = {
open: false,
showPlayButton: true,
showGalleryPlayButton: false,
showFullscreenButton: true,
showGalleryFullscreenButton: false,
currentImage: 0,
test: 0,
player: [],
data: data
};
console.log("Images: ", this.state.data);
this.openLightbox = this.openLightbox.bind(this);
this._renderImages = this._renderImages.bind(this);
this._onSlide = this._onSlide.bind(this);
this._onReady = this._onReady.bind(this);
}
state = {
isOpen: false
};
openModal = event => {
console.log(event.target);
this.setState({ isOpen: true });
};
openLightbox(index, event) {
// console.log('index',index);
event.preventDefault();
// this.setState({
// isOpen: true,
// currentImage: index
// });
this.setState(
prevState => {
return {
currentImage: index,
isOpen: true
};
},
() => {
console.log("currentImage", this.state.currentImage);
console.log("event", index);
}
);
}
hideModal = () => {
this.setState({ isOpen: false });
};
_renderImages(item) {
return (
<div className="images image-gallery-image">
<div className="images image-wrapper">
<h1>{this.state.currentImage}</h1>
<img src={item.img} alt="" className="images multimedia_image" />
<span className="images image-gallery-description">{item.desc}</span>
</div>
</div>
);
}
_onReady(event) {
const player = this.state.player;
player.push(event.target);
this.setState({
player: player
});
}
_onSlide() {
this.state.data.images.forEach(player => {});
}
handleImageLoad(event) {
console.log("Image loaded ", event.target);
}
render() {
var openLightbox = this.openLightbox;
var currentImage = this.state.currentImage;
const number = this.state.currentImage;
return (
<div className="images row">
<div className="images col-xs-12 col-sm-12 col-md-12 col-lg-12">
<div className="images title">{this.state.data.title}</div>
</div>
<div className="images col-xs-12 col-sm-12 col-md-12 col-lg-12">
<div className="images row">
{this.state.data.images.map((object, i) => {
let backgroundImage = {
backgroundImage: "url(" + object.thumbnail + ")",
top: 0,
right: 0,
left: 0,
bottom: 0,
position: "absolute",
flex: 1,
backgroundPosition: "center",
backgroundSize: "cover",
zIndex: 1
};
return (
<div
className="images item col-xs-4 col-sm-4 col-md-3 col-lg-3 images__single-item"
key={i}
>
<div
className="images multimedia_button"
onClick={e => this.openLightbox(i, e)}
>
<div style={backgroundImage} />
</div>
</div>
);
})}
</div>
</div>
<Modal isOpen={this.state.isOpen} onRequestHide={this.hideModal}>
<button
type="button"
className="images player_button_close"
onClick={this.hideModal}
>
X
</button>
<ModalBody>
<ImageGallery
items={this.state.data.images}
startIndex={this.state.currentImage}
slideInterval={2000}
showPlayButton={false}
showFullscreenButton={false}
onImageLoad={this.handleImageLoad}
onSlide={this._onSlide}
showIndex={true}
renderItem={this._renderImages}
/>
</ModalBody>
</Modal>
</div>
);
}
}
I found a solution when I needed to reinitialize current element. It can be useful for some other dev.
The answer is A “key” a special string attribute when we create lists of elements.
Whenever "key" is going change element is rerender. So
this.state = {
open: false,
showPlayButton: true,
showGalleryPlayButton: false,
showFullscreenButton: true,
showGalleryFullscreenButton: false,
currentImage: 0,
test: 0,
player: [],
data: data,
number:0
};
openLightbox(index, event) {
event.preventDefault();
this.setState(
prevState => {
return {
currentImage: index,
isOpen: true,
number:prevState.number+1
};
},
() => {
console.log("currentImage", this.state.currentImage);
console.log("event", index);
}
);
And here we neet to add our key={this.state.number}
<Modal isOpen={this.state.isOpen} onRequestHide={this.hideModal}>
<button
type="button"
className="images player_button_close"
onClick={this.hideModal}
>
X
</button>
<ModalBody>
<ImageGallery
key={this.state.number}
items={this.state.data.images}
startIndex={this.state.currentImage}
slideInterval={2000}
showPlayButton={false}
showFullscreenButton={false}
onImageLoad={this.handleImageLoad}
onSlide={this._onSlide}
showIndex={true}
renderItem={this._renderImages}
/>
</ModalBody>
</Modal>

Updating props in note taking app in React

I'm stuck on my note taking app. Basically the App component passes in data to the NoteEntry component through props. Yet I can't figure out how to edit the previous passed text through props within each NoteEntry instance when I click the "edit" button. The edit button is supposed to bring up text inputs to change the content by updating the text and then pressing the save button. Any tips on how to go about it?
class App extends Component {
constructor(props) {
super(props);
this.state = {
notes: [],
title: "",
details: ""
}
this.updateTitle = this.updateTitle.bind(this);
this.updateDetails = this.updateDetails.bind(this);
this.submitHandler = this.submitHandler.bind(this);
this.deleteHandler = this.deleteHandler.bind(this);
}
updateTitle(event) {
this.setState({ title: event.target.value });
}
updateDetails(event) {
this.setState({ details: event.target.value });
}
submitHandler(e) {
e.preventDefault();
if (!this.state.title.length || !this.state.details.length) {
return;
}
const newNote = {
newTitle: this.state.title,
newDetails: this.state.details
}
this.setState(prevState => ({
notes: prevState.notes.concat(newNote),
title: "",
details: ""
}))
}
deleteHandler(id) {
this.setState(prevState => ({
notes: prevState.notes.filter(el => el !== id)
}))
}
render() {
return (
<div className="container">
<h1 className="title">React Notes App</h1>
<NoteForm
titleValue={this.state.title}
detailsValue={this.state.details}
titleHandle={this.updateTitle}
detailsHandle={this.updateDetails}
onSubmit={this.submitHandler}
/>
<div className="entry-section">
{this.state.notes.map((note, i) => (
<NoteEntry
key={i}
title={note.newTitle}
details={note.newDetails}
deleteNote={this.deleteHandler.bind(this, note)}
/>
))}
</div>
</div>
);
}
}
const NoteForm = (props) => {
return (
<div>
<form className="form-section">
<input
className="title-input"
type="type"
placeholder="Title"
value={props.titleValue}
onChange={props.titleHandle}
/>
<br />
<textarea
className="details-input"
cols="20"
rows="3"
placeholder="Details"
value={props.detailsValue}
onChange={props.detailsHandle}
/>
<br />
<button
className="input-button"
onClick={props.onSubmit}
>Add Note</button>
</form>
</div>
)
}
class NoteEntry extends Component {
constructor(props) {
super(props);
this.state = {
display: false,
editTitle: this.props.title,
editDetails: this.props.details,
editing: false
}
this.displayToggle = this.displayToggle.bind(this);
this.edit = this.edit.bind(this);
this.save = this.save.bind(this);
}
displayToggle() {
this.setState(prevState => ({
display: !prevState.display
}))
}
edit() {
this.setState({
editing: true
})
}
save() {
let titleVal = this.refs.updateTitle.value;
let detailsVal = this.refs.updateDetails.value;
this.setState({
editTitle: titleVal,
editDetails: detailsVal,
editing: false
})
}
render() {
return (
<div className="entry">
<div className="entry-header" onClick={this.state.editing ? null : this.displayToggle}>
{this.state.editing ? (
<input ref="updateTitle" className="edit-title" type="text" />
) : (
<h2 className="entry-title">{this.props.title}</h2>
)}
<p className="timestamp">{this.displayTime}</p>
</div>
<hr />
<div className={"entry-content " + (!this.state.display ? "hide-details" : null)}>
{this.state.editing ? (
<textarea ref="updateDetails" className="edit-details" cols="10" rows="2"></textarea>
) : (
<p className="details">{this.props.details}</p>
)}
<div className="entry-buttons">
{this.state.editing ? (
<button className="save" onClick={this.save}>Save</button>
) : (
<button className="edit" onClick={this.edit}>Edit</button>
)
}
<button className="delete" onClick={this.props.deleteNote}>Delete</button>
</div>
</div>
</div>
)
}
}
You can do by pass data from child to parent component as mention it in comment.
In you case NoteEntry add onEditNote props. This props use for function by parent (App component) and use by onClick edit button.
<NoteEntry
...
onEditNote={this.handleClickEdit}
/>
then in class NoteEntry
<button className="edit" onClick={() => this.props.handleClickEdit(this.props.title, this.props.detail)}>Edit</button>
So, handleClickEdit handle by App component and set it to your state
handleClickEdit = (_title, _detail) => {
this.setState({title: _title, details: _detail});
}
Now, your NoteForm component able to edit.

How to run a search query on the youtube api, recieving error

I am writing my first React application, and I am having issues with the Youtube API. I have written a seperate Youtube search function like:
var searchYouTube = (options, callback) => {
$.get('https://www.googleapis.com/youtube/v3/search', {
key: window.YOUTUBE_API_KEY,
q: options.query,
maxResults: options.max,
}).done(function(data) {
console.log(data);
callback(data);
});
};
window.searchYouTube = searchYouTube;
It is triggered every time there is a change in the search input. You can see this component in my app.jsx:
class App extends React.Component {
constructor() {
super();
this.state = {
videos: exampleVideoData,
currentVideo: exampleVideoData[0]
};
}
renderSearch(term) {
console.log($(term.target).val());
this.setState({
videos: searchYouTube({query:$(term.target).val(),max:5})
});
}
setVideo(video) {
this.setState({
currentVideo: video
});
}
render() {
return (
<div>
<nav className="navbar">
<div className="col-md-6 offset-md-3">
<div><Search renderSearch={this.renderSearch.bind(this)}/></div>
</div>
</nav>
<div className="row">
<div className="col-md-7">
<div><VideoPlayer currentVideo={this.state.currentVideo}/></div>
</div>
<div className="col-md-5">
<div><VideoList videos={this.state.videos} setVideo={this.setVideo.bind(this)}/></div>
</div>
</div>
</div>
);
}
}
// In the ES6 spec, files are "modules" and do not share a top-level scope
// `var` declarations will only exist globally where explicitly defined
window.App = App;
Finally, the error I get is:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "required",
"message": "Required parameter: part",
"locationType": "parameter",
"location": "part"
}
],
"code": 400,
"message": "Required parameter: part"
}
}
Any suggestions on what I need to retool? I believe it the structure of the searchYoutube function. Maybe I am missing a parameter?
The console log is clear :
"message": "Required parameter: part",
Means you have to add part in your options. And I suggest to add the following:
var searchYouTube = (options, callback) => {
$.get('https://www.googleapis.com/youtube/v3/search', {
key: window.YOUTUBE_API_KEY,
q: options.query,
part: 'snippet', // 💥 this one was missing
maxResults: options.max,
}).done(function(data) {
console.log(data);
callback(data);
});
};
It will work. Don't worry! and demo is below :
const { Component } = React;
class SearchBar extends Component {
state = { term: '' };
render() {
return (
<div className="search-bar">
<input
value={this.state.term}
onChange={event => this.onInputChange(event.target.value)}
/>
</div>
);
}
onInputChange = term => {
this.setState({ term });
this.props.onSearchTermChange(term);
};
}
const VideoDetail = ({ video }) => {
if (!video) {
return <div>Loading...</div>;
}
const videoId = video.id.videoId;
const url = `https://www.youtube.com/embed/${videoId}`;
return (
<div className="video-detail col-md-8">
<div className="embed-responsive embed-responsive-16by9">
<iframe className="embed-responsive-item" src={url} />
</div>
<div className="details">
<div>
{video.snippet.title}
</div>
<div>
{video.snippet.description}
</div>
</div>
</div>
);
};
const VideoListItem = ({ video, onVideoSelect }) => {
const imageUrl = video.snippet.thumbnails.default.url;
return (
<li onClick={() => onVideoSelect(video)} className="list-group-item">
<div className="video-list media">
<div className="media-left">
<img className="media-object" src={imageUrl} />
</div>
<div className="media-body">
<div className="media-heading">
{video.snippet.title}
</div>
</div>
</div>
</li>
);
};
const VideoList = props => {
const videoItems = props.videos.map(video => {
return (
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.etag}
video={video}
/>
);
});
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
);
};
const InvalidApiKey = () => (<h1>Sorry you do not give a valid YOUTUBE API key. Refresh the page or Run the snippet again and give a valid API key. </h1>)
class App extends Component {
state = {
videos: [],
selectedVideo: null,
error: false
};
componentDidMount() {
this.videoSearch('Sport');
}
searchYouTube(options, callback) {
$.get('https://www.googleapis.com/youtube/v3/search', {
key: this.props.youtubeApiKey,
q: options.query,
part: 'snippet',
maxResults: options.max
}).done(function(data) {
callback(data);
}).fail(() => this.setState({error: true}))
}
videoSearch = (term) => {
this.searchYouTube({ key: this.props.youtubeApiKey, term: term }, data => {
this.setState({
videos: data.items,
selectedVideo: data.items[1]
});
});
}
render() {
// const videoSearch = _.debounce(term => {
// this.videoSearch(term);
// }, 300);
if (this.state.error) return <InvalidApiKey />
return (
<div>
<SearchBar onSearchTermChange={this.videoSearch} />
<VideoDetail video={this.state.selectedVideo} />
<VideoList
onVideoSelect={selectedVideo => this.setState({ selectedVideo })}
videos={this.state.videos}
/>
</div>
);
}
}
const youtubeApiKey = prompt(
'Give a valid YOUTUBE API KEY and everything should work: '
);
ReactDOM.render(
youtubeApiKey
? <App youtubeApiKey={youtubeApiKey} />
: <InvalidApiKey />,
document.querySelector('#app')
);
.search-bar {
margin: 20px;
text-align: center;
}
.search-bar input {
width: 75%;
}
.video-item img {
max-width: 64px;
}
.video-detail .details {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.list-group-item {
cursor: pointer;
}
.list-group-item:hover {
background-color: #eee;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<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="app" />

React-modal setstate not working

export class StartContainer extends Component {
constructor(props) {
super(props);
this.state = {
showModal: false
};
}
handleOpenModal = () => {
console.log("Here")
//this.setState({ showModal: true }, () =>{ console.log(this.state) });
this.setState(() => {
console.log("Changing state")
return { showModal: true }
});
}
handleCloseModal = () => {
console.log(this.state.showModal)
this.setState( );
}
render() {
console.log(this.state)
return (
<div>
<StartComponent handleModalOpen={this.handleOpenModal} />
<ReactModal
isOpen={this.state.showModal}
contentLabel="Minimal Modal Example"
>asda
<button onClick={this.handleCloseModal}>Close Modal</button>
</ReactModal>
</div>
)
}
}
So I am trying to integrate react-modal into my project.
The this.setState() method is not called I see no console log, neither when I pass a callback to the setState() methpd.
Can somebody help me please?
Thx for your time!
UPDATE -- Start component code.
export const StartComponent = (props) => (
<div className="start-page">
<div className="container">
<div className="row">
<div className="col-sm-6">
<NavLink to="/start/klarungsfalle">Einträge prüfen</NavLink>
</div>
<div className="col-sm-6" >
<NavLink onClick={props.handleModalOpen} style={{ background: "#aac4d3", cursor: "default" }} to="/">Einträge verfügen</NavLink>
</div>
</div>
</div>
</div>
);
Plus I have to mention that I am also using redux.
Your code seems to work for me. I just set up the <StartComponent /> and it looks like the state is being set how you want.
Try the following snippet which uses your code:
Alternatively you can check out this CodePen Demo.
const { HashRouter, NavLink } = ReactRouterDOM;
const App = () => (
<HashRouter>
<Modal />
</HashRouter>
);
const StartComponent = ({currentState, handleModalOpen, handleNix}) => (
<div className="start-page">
<div className="container">
<div className="row">
<div className="col-sm-6">
<NavLink to="/start/klarungsfalle" onClick={handleNix}>Einträge prüfen</NavLink>
</div>
<div className="col-sm-6">
<NavLink
onClick={handleModalOpen}
style={{ background: "#aac4d3", cursor: "default" }}
to="/"
>
Einträge verfügen
</NavLink>
</div>
</div>
<div className='row justify-center'>
<div className='col-xs-12'>
<div>
<code>Modal</code> state
<pre>{JSON.stringify(currentState)}</pre>
</div>
</div>
</div>
</div>
</div>
);
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false
};
}
handleOpenModal = () => {
console.log("Here");
this.setState(() => {
console.log(`Changing state to 'showModal: ${this.state.showModal}'`);
return { showModal: true };
});
};
handleNix = () => {
alert("hier gibt's nichts");
}
handleCloseModal = () => {
console.log(this.state.showModal);
this.setState(() => {
console.log(`Changing state to 'showModal: ${this.state.showModal}'`);
return { showModal: false };
});
};
render() {
console.log(this.state);
return (
<div className="container">
<StartComponent
handleModalOpen={this.handleOpenModal}
handleNix={this.handleNix}
currentState={this.state}/>
<ReactModal
isOpen={this.state.showModal}
contentLabel="Minimal Modal Example">
<div className="flex columns-center">
<div className="note">
The modal hides the Stack Overflow console. Look behind the modal
or open your JS console.
</div>
<div className="flex">
<div>
<code>Modal</code> state
<pre>{JSON.stringify(this.state)}</pre>
</div>
<button
className="btn btn-sm btn-danger"
onClick={this.handleCloseModal}>
Close Modal
</button>
</div>
</div>
</ReactModal>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
.flex {
display: flex;
}
.justify-center {
justify-content: center;
}
.space-around {
justify-content: space-around;
}
.columns-center {
flex-direction: column;
align-items: center;
}
.note {
font-size: 0.7em;
margin-bottom: 1rem;
}
.btn:after {
content: "\01F436";
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" />
<div id="app"></div>
<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>
<script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-modal/2.3.2/react-modal.min.js"></script>
The problem seems to be some missing bindings needed for es6 when working with events. So for handlers to access state just put these bindings in you constructor :
constructor() {
super();
this.state = {
showModal: false
};
// bindings
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
}
Anonymous functions are not needed there :
handleOpenModal() {
this.setState({showModal: true});
}
handleCloseModal() {
this.setState({showModal: false});
}
You have to bind functions to use 'this' keyword
constructor() {
super();
this.state = {
showModal: false
};
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
}
Passing a callback into setState means that when setState finishes it'll run that function next; setState is async, but that's not going to help you here. It might be helpful to provide the code for your StartComponent component as it's likely your handler function isn't being called.
try onClick={() => props.handleModalOpen()}
Note: just a suggestion, consider naming your props the same thing in child as in parent cause handleModalOpen and handleOpenModal can get confusing.

Resources