this.setState() not a function after successful API call method - javascript

This question has come up quite a few times on here, I know, but none of the solutions have worked for me. So, I'm making a call to the iTunes API with a fetch request, based on user input in a React app. The initial call/submit works just fine. However, when I try to do a second search, I get a this.setState is not a function error as soon as I type in the input box. I've tried binding this in all the methods and in all the ways one can, but no success in getting it to work. Any ideas?
class App extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
artist: null,
albums: []
};
this.handleChange = this.handleChange.bind(this);
this.getSearchResults = this.getSearchResults.bind(this);
}
handleChange(event) {
this.setState({ query: event.target.value });
}
getSearchResults() {
console.log('query!!! ' + this.state.query);
const URL_TEMPLATE = "https://itunes.apple.com/search?entity=album&limit=25&term={query}";
let url = URL_TEMPLATE.replace('{query}', this.state.query);
fetch(url)
.then((response) => {
let data = response.json();
return data;
})
.then((data) => {
console.log(data.results);
this.setState ({
albums: data.results
});
})
.catch((err) => {
console.log(err);
});
this.setState({ query: '' });
}
render() {
return (
<div className="container">
<hr />
<div className="col-lg-6">
<div className="input-group">
<input type="text"
value={this.state.query}
onChange={this.handleChange}
className="form-control" placeholder="Search for..." />
<span className="input-group-btn">
<button
onClick={() => this.getSearchResults()}
className="btn btn-default" type="button">Go
</button>
</span>
</div>
</div>
<hr />
<div>
Albums: {this.state.albums}
</div>
</div>
);
}}
edit: I should mention that it gives me the error on the handleChange() method, since that's what deals with the input.

Here is the complete working code
import React from 'react';
class TestJS extends React.Component {
constructor(props) {
super(props);
this.state = {
query: '',
artist: null,
albums: []
};
this.handleChange = this.handleChange.bind(this);
this.getSearchResults = this.getSearchResults.bind(this);
}
handleChange(event) {
this.setState({ query: event.target.value });
}
getSearchResults() {
console.log('query!!! ' + this.state.query);
const URL_TEMPLATE = "https://itunes.apple.com/search?entity=album&limit=25&term={query}";
let url = URL_TEMPLATE.replace('{query}', this.state.query);
fetch(url)
.then((response) => {
let data = response.json();
return data;
})
.then((data) => {
console.log(data.results);
this.setState({
albums: data.results
});
})
.catch((err) => {
console.log(err);
});
this.setState({ query: '' });
}
render() {
let plus5 = [];
if(!!this.state.albums && this.state.albums.length > 0){
this.state.albums.map((val, i, arr) => {
plus5.push(<div key={i}>{val.artistId}</div>);
});
}
return (
<div className="container">
<hr />
<div className="col-lg-6">
<div className="input-group">
<input type="text"
value={this.state.query}
onChange={(e) => this.handleChange(e)}
className="form-control" placeholder="Search for..." />
<span className="input-group-btn">
<button
onClick={() => this.getSearchResults()}
className="btn btn-default" type="button">Go
</button>
</span>
</div>
</div>
<hr />
<div>
{plus5}
</div>
</div>
);
}
}
export default TestJS;
I've just rendered artistId. You can render anything you want
Thanks!

Does arrow function work?
<input type="text"
value={this.state.query}
onChange={(e) => this.handleChange(e)}
className="form-control" placeholder="Search for..." />
And you cannot call setState() like this:
this.setState = {
albums: data.results
};
please change to :
this.setState({
albums: data.results
});

I think since you are inside the closure, this is not available inside
.then((data) => {
console.log(data.results);
this.setState = {
albums: data.results
};
})
Try assigning let that = this outside and before the fetch call.
And then
.then((data) => {
console.log(data.results);
that.setState = {
albums: data.results
};
})

Related

React - form - unable to update/edit value in input/textarea - writing disable

Here is the code:
import React from 'react';
class EditEntry extends React.Component {
constructor(props) {
super(props);
this.state = {
entry: '',
description: '',
item: [],
error: null
};
this.handleChangeEntry = this.handleChangeEntry.bind(this);
this.handleChangeDescription = this.handleChangeDescription.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
const { match: { params } } = this.props;
const { id } = params;
fetch(`/api/entries/${id}`, {
method: 'GET'
})
.then(response => response.json())
.then(
data => {
this.setState({
item: data
});
},
error => {
this.setState({
error
});
});
}
handleChangeEntry(e) {
this.setState({ entry: e.target.value });
}
handleChangeDescription(e) {
this.setState({ description: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
const { entry, description } = this.state;
const { match: { params } } = this.props;
const { id } = params;
fetch(`/api/entries/${id}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(entry, description)
})
.then(response => {
if (response.ok) {
window.location.assign("/");
} else {
throw new Error('No response.');
}
},
error => {
this.setState({
error
});
});
}
render() {
const { item } = this.state;
const itemEditForm = item.map(i => {
return <div key={i.id}>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="entry">Entry</label>
<input
id="entry"
name="entry"
type="text"
className="form-control"
required
value={i.entry}
onChange={this.handleChangeEntry} />
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<textarea
id="description"
name="description"
type="text"
className="form-control"
required
rows="5"
value={i.description}
onChange={this.handleChangeDescription} />
</div>
<button type="submit" className="btn btn-secondary btn-sm mb-sm-2">Submit</button>
</form>
</div>
});
return (
<div>
{itemEditForm}
</div>
);
}
}
export default EditEntry;
I can't edit the data contained in:
<input id="entry" (...) />
<textarea id="description" (...) />
There is data, there is a cursor, but these fields are disabled for editing.
I fetch an array with one object from the database (componentDidMount()). I know there is another way to access the data contained in an object. But that way in this project doesn't work. Although in my another project it works. Strange.
You are setting the "entry" and "description" properties in the onChange handler methods. but using the i.entry and i.description in the value property of the input. Please try changing it and maybe if you have multiple items in the item array, you can try something like below to handle state.
Component:
<input
id="name"
name="name"
type="text"
className="form-control"
required
value={i.name}
onChange={(e) => this.handleChangeName(e, i.id)}
/>
JS:
handleChangeName(e, id) {
console.log(e.target.value);
const newState = {
item: this.state.item.map((x) =>
x.id != id ? x : { id: id, name: e.target.value }
)
};
this.setState(newState);
}
Also, please find a working example based on your code on the CodeSandbox Link
Thanks.

How can I access array when I put it in this.state?

I am new to react and have read the react docs on how to make an Ajax call. They made it look so simple when they were returning the json information but it doesn't work for me. I am trying to call the json information and set it to this.state.stockSymbol but every time I try to access the information I use typeof and it returns an object. I can see the json information clearly when I console.log it but for some reason it won't update in my getSymbol function. I think it has to to with the async call but I'm not totally understanding it. Can someone point me in the right direction?
Here is my code:
class Stocks extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
stockSymbol: [],
isLoaded: false
}
}
typeSymbol = (e) => {
this.setState({
userInput: e.target.value.toUpperCase()
}, (e) => {
console.log(this.state.userInput)
})
}
getSymbol = (e) => {
e.preventDefault(),
this.setState({
stockSymbol: this.state.stockSymbol
}, () => {
console.log(typeof this.state.stockSymbol)
console.log(this.state.stockSymbol)
})
}
componentDidMount() {
fetch(`https://finnhub.io/api/v1/stock/symbol?exchange=US&token=${key}`)
.then(res => res.json())
.then(
(results) => {
this.setState({
isLoaded: true,
stockSymbol: results
});
console.log(results)
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { stockSymbol, userInput, results } = this.state
stockSymbol.map((stock, i) => {
if (userInput === this.state.stockSymbol) {
return (
console.log('same'),
<span className="symbol" key={i} onSubmit={this.getSymbol}>
{stock.displaySymbol}
</span>
);
}
})
return (
<div className="enterstock">
<h1 className="title">Enter Stock Symbol</h1>
<span className="symbol">{this.state.userInput}</span>
<form className="inputfields" onSubmit={this.getSymbol}>
<input type="text" className="symfields" name="symbolname" onChange={this.typeSymbol}></input>
<button type="submit" className="btn">Send</button>
</form>
</div >
)
}
}
ReactDOM.render(
<Stocks />,
document.getElementById('root')
)
There are couple of issues but not with fetching data.
What you are trying to do is filter stock symbols but you are comparing userInput with Stock symbol rather than with name of each stock
getSymbol doesn't need to setState as you already have set the state after fetching data.
Here is a sandbox that you can try out which does exactly what you are looking for: https://codesandbox.io/s/twilight-dream-jwe7g?file=/src/index.js
Search "leanne graham" to test out
Most likely the problem is with your render function, the mapped isn't actually being rendered. You have to return a single jsx expression that contains everything.
render() {
const { stockSymbol, userInput, results } = this.state
const symbols = stockSymbol.map((stock, i) => {
// removed your if statement, it didn't make sense
return (
<span className="symbol" key={i} onSubmit={this.getSymbol}>
{stock.displaySymbol}
</span>
);
})
return (
<>
{symbols}
<div className="enterstock">
<h1 className="title">Enter Stock Symbol</h1>
<span className="symbol">{this.state.userInput}</span>
<form className="inputfields" onSubmit={this.getSymbol}>
<input type="text" className="symfields" name="symbolname" onChange={this.typeSymbol}></input>
<button type="submit" className="btn">Send</button>
</form>
</div >
</>
)
}

Changing parent state after updating childs prop component in React?

I'm newish to react. I have a fetch call in my App Component that I assign to a state. I pass that state as a prop along with a function to make a post to a child component. In my child component you can post/delete to alter the props, currently don't have a push() to add the new contact/prop. Is there a way to alter the parent component's state after I change the childs props? is there a better way to do this?
I'm trying to get the post action to update the state on the App.
App code
class App extends Component {
constructor() {
super();
this.state= {
contacts:[],
addModalShow: false,
modalIsOpen: false
}
}
componentDidMount() {
var request = new Request('http://localhost:3000/', {
method: "GET",
});
fetch(request)
.then((res) => {
res.json()
.then((data) => {
this.setState({
contacts: data.rows
})
})
})
}
toggleModal() {
this.setState({
modalIsOpen: ! this.state.modalIsOpen
})
}
addContact(event) {
this.toggleModal()
event.preventDefault();
let contactData = {
first: this.refs.first.value,
last: this.refs.last.value,
phone: this.refs.phone.value,
email: this.refs.email.value,
};
var request = new Request('http://localhost:3000/add', {
method: "POST",
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(contactData)
});
console.log(this.state)
fetch(request)
.then((res) => {
res.json()
.then((data) => {
})
})
.catch((err) => {
console.log(err)
})
}
render() {
return (
<Container>
{console.log(this.state)}
<AddContact addContact={this.addContact} contacts={this.state.contacts} />
<ContactList contacts={this.state.contacts} />
<Contacts contacts={this.state.contacts}/>
</Container>
);
}
}
export default App;
Child component
class AddContact extends Component {
constructor(props) {
super(props);
this.state = {
contacts: [],
modalIsOpen: false,
}
}
toggleModal() {
this.setState({
modalIsOpen: ! this.state.modalIsOpen
})
}
render() {
return(
<Container>
<div className='header'>
<h1>
My Contacts
<button className='addContactButton' onClick={this.toggleModal.bind(this)}>+</button>
</h1>
<hr />
</div>
<Modal isOpen={this.state.modalIsOpen}>
<form ref='addContact' >
<div className='addContactHeader'>
<button className='saveButton' onClick={this.props.addContact.bind(this)}>Save</button>
<button className='cancelButton' onClick={this.toggleModal.bind(this)}>Cancel</button>
</div>
<div id="circle">
Add Photo
</div>
<div className="inputFields">
<div className='nameInputs'>
<input type='text' ref='first' placeholder='first name' />
<input type='text' ref='last' placeholder='last name' />
</div>
<div className='extraInputs' >
<input type='text' ref='phone' placeholder='phone' />
<input type='text' ref='email' placeholder='email' />
</div>
</div>
</form>
</Modal>
</Container>
)
}
}
Thanks for your time
You could use a callback function in order to update the state on the parent component (Another approach would be to use Redux updating the value in the Store, that way both components could have access to the value), here's how you could use the callback (With a little bit of ES6 refactor):
App:
class App extends Component {
state= {
contacts:[],
addModalShow: false,
modalIsOpen: false
}
componentDidMount() {
let request = new Request('http://localhost:3000/', {
method: "GET",
});
fetch(request)
.then((res) => {
res.json()
.then((data) => { this.setState({ contacts: data.rows }) })
})
}
toggleModal = () => {
this.setState({ modalIsOpen: ! this.state.modalIsOpen })
};
addContact = event => {
this.toggleModal()
event.preventDefault();
let contactData = {
first: this.refs.first.value,
last: this.refs.last.value,
phone: this.refs.phone.value,
email: this.refs.email.value,
};
let request = new Request('http://localhost:3000/add', {
method: "POST",
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(contactData)
});
fetch(request)
.then((res) => {
res.json()
.then((data) => {
})
})
.catch((err) => {
console.log(err)
})
};
changeContacts = (newData) => {
this.setState({ contacts: newData });
};
render() {
const { contacts } = this.state;
return (
<Container>
<AddContact
addContact={this.addContact}
contacts={contacts}
onChildAction={this.changeContacts}
/>
<ContactList contacts={contacts} />
<Contacts contacts={contacts}/>
</Container>
);
}
}
export default App;
AddContacts:
class AddContact extends Component {
state = {
contacts: [],
modalIsOpen: false,
}
toggleModal = () => {
this.setState({ modalIsOpen: ! this.state.modalIsOpen })
};
// Here is where you'll send the info for the change of the prop
changeProp = e => {
const { onChildAction } = this.props;
onChildAction('Your new state/prop value here')
addContact(e);
};
render() {
const { changeProp } = this.props;
const { modalIsOpen } = this.state;
return(
<Container>
<div className='header'>
<h1>My Contacts
<button className='addContactButton' onClick={this.toggleModal}>+</button>
</h1>
<hr />
</div>
<Modal isOpen={modalIsOpen}>
<form ref='addContact' >
<div className='addContactHeader'>
<button className='saveButton' onClick={changeProp}>Save</button>
<button className='cancelButton' onClick={this.toggleModal}>Cancel</button>
</div>
<div id="circle">Add Photo</div>
<div className="inputFields">
<div className='nameInputs'>
<input type='text' ref='first' placeholder='first name' />
<input type='text' ref='last' placeholder='last name' />
</div>
<div className='extraInputs' >
<input type='text' ref='phone' placeholder='phone' />
<input type='text' ref='email' placeholder='email' />
</div>
</div>
</form>
</Modal>
</Container>
)
}
}
The last thing you need to do is decide where you want the change of the state/prop to be fire. Hope this helps.
to handle the parent from child you need to bind this to the child
Parent Component
class Component extends React.Component {
constructor(props) {
super(props)
this.state= {
contacts:[],
addModalShow: false,
modalIsOpen: false
}
this.addContact = this.addContact.bind(this);
}
render() {
...
return <AddContact addContact = {this.addContact} />
}
addContact(event) {
...
alert('one contact added');
...}
}
inside AddContact Component :
you can call this.props.addContact() to excute the parent function

AXIOS PUT request returns a 200 code but doesn't update content?

So I have a a component that returns a 200 code but for some reason the content does not update at all after I click the submit button. My goal is to update the 4 divs inside the form after submitting the form. The course state contains properties that contain info about each course, those properties are _id, description, estimatedTime, materialsNeeded and title.
Can someone help?
class UpdateCourse extends Component {
constructor(props) {
super(props);
this.state = {
course: []
};
this.handleSubmit = this.handleSubmit.bind(this);
}
change = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = event => {
const {
match: { params }
} = this.props;
event.preventDefault();
const updateCourse = {
title: this.state.course.title,
description: this.state.course.description,
estimatedTime: this.state.course.estimatedTime,
materialsNeeded: this.state.course.materialsNeeded
};
axios({
method: "put",
url: `http://localhost:5000/api/courses/${params.id}`,
auth: {
username: window.localStorage.getItem("Email"),
password: window.localStorage.getItem("Password")
},
data: updateCourse
})
.then(response => {
//if the response came back as 204 then alert the user that the course was successfully updated, if another code came back then redirect them to the error handler
if (response.status === 204) {
alert("The course has been successfully updated!");
this.props.history.push("/");
} else {
throw new Error();
}
})
.catch(err => {
//use a catch method to catch the errors and display them is the status code comes back as 400
console.log("CATCH =", err.response.data.errors);
this.setState({
//if there were errors, then set the errors state in react to the error messages that came from the REST API
errors: err.response.data.errors
});
});
};
componentDidMount() {
const {
match: { params }
} = this.props;
axios
.get(`http://localhost:5000/api/courses/${params.id}`)
.then(results => {
this.setState({
course: results.data
});
});
}
render() {
return (
<div>
<div>
<form onSubmit={this.handleSubmit}>
<div>
<input
id="title"
name="title"
type="text"
className="input-title course--title--input"
placeholder="Course title..."
defaultValue={this.state.course.title}
onChange={e => this.change(e)}
/>
</div>
<div>
<textarea
id="description"
name="description"
placeholder={this.state.course.description}
defaultValue={this.state.course.description}
onChange={e => this.change(e)}
/>{" "}
</div>
<div>
<input
id="estimatedTime"
name="estimatedTime"
type="text"
className="course--time--input"
placeholder="Hours"
defaultValue={this.state.course.estimatedTime}
onChange={e => this.change(e)}
/>
</div>
<div>
<textarea
id="materialsNeeded"
name="materialsNeeded"
placeholder={this.state.course.materialsNeeded}
defaultValue={this.state.course.materialsNeeded}
onChange={e => this.change(e)}
/>
</div>
</form>
</div>
</div>
);
}
}
Please update with this:
constructor(props) {
super(props);
this.state = {
course: {}
};
this.handleSubmit = this.handleSubmit.bind(this);
this.change = this.change.bind(this);
}
change = e => {
const obj = { [e.target.name]: e.target.value };
const course = Object.assign({}, this.state.course, obj);
this.setState({
course
});
};

can't get the function transferred via props in React

I have the component ExpenseForm
class ExpenseForm extends Component {
state = {
description: '',
amount: '',
note: '',
createdAt: moment(),
calendarFocused: false,
error: ''
};
onInputChange = (e) => {
const prop = e.target.name;
const val = e.target.value;
if(prop === 'amount') {
if(!val || val.match(/^\d{1,}(\.\d{0,2})?$/)) {
this.setState(() => ({ [prop]:val }));
}
} else {
this.setState(() => ({ [prop]:val }));
}
};
onDateChange = (createdAt) => {
if(createdAt) {
this.setState(() => ({createdAt}));
}
};
onFocusChange = ({focused}) => {
this.setState(() => ({calendarFocused: focused}))
};
onFormSubmit = (e) => {
e.preventDefault();
const { description, amount, note, createdAt } = this.state;
if(!description || !amount) {
this.setState(() => ({error: 'Please provide description and amount'}));
} else {
this.setState(() => ({error: ''}));
console.log(this.props.onSubmit) //<<< here i get undefined
this.props.onSubmit({
description,
amount: parseFloat(amount, 10) * 100,
createdAt: createdAt.valueOf(),
note
});
}
};
render() {
console.log(this.props) //<<< here I see the object with the prop onSubmit, where lies the function onEditSubmit
return (
<div>
<h1>Expense Form</h1>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.onFormSubmit}>
<input
onChange={this.onInputChange}
value={this.state.description}
name="description"
type="text"
placeholder="Description"
autoFocus
/>
<input
onChange={this.onInputChange}
value={this.state.amount}
name="amount"
type="text"
placeholder="Amount" />
<SingleDatePicker
date={this.state.createdAt}
onDateChange={this.onDateChange}
focused={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
numberOfMonths={1}
isOutsideRange={() => false }
/>
<textarea
onChange={this.onInputChange}
value={this.state.note}
name="note"
placeholder="Add a note for your expense (optional)"
></textarea>
<button>Add Expense</button>
</form>
</div>
)
}
}
I use this component in two places
The first one is here
function AddExpensePage({ addExpense, history }) {
const onAddSubmit = (data) => {
addExpense(data);
history.push('/');
};
return (
<div>
<h1>AddExpensePage</h1>
<ExpenseForm
onSubmit={onAddSubmit}
/>
</div>
)
}
And the second one is here
function EditPage(props) {
const onEditSubmit = () => {
console.log('edit submit')
};
return (
<div>
<h1>Edit Expense Page {props.match.params.id}</h1>
<ExpenseForm
onSumbit={onEditSubmit}
/>
</div>
)
}
In the first case everything works fine and I invoke the function transferred via props (onAddSubmit).
In the second one I get the error _this.props.onSubmit is not a function.
When I console.log the props of ExpenseForm I see in the object the function I transferred (onEditSubmit). But when I make console.log before calling this.props.onSubmit I see undefined in my console.
So I can't understand why this's happening.
I think that is just a typo:
onSumbit={onEditSubmit}
Should be
onSubmit={onEditSubmit}

Categories

Resources