async function passed as props, event handler could not call this function - reactjs

I'm passing a async function from App to Header to Form. I cant seems to call that function with submit event handler even with the use of arrow function.
I've tried fat arrow function. The only working way is to move the function down to Form which it work but not ideal as I need the data to be stored in App state. when I console.log(result) I get undefined
class Form extends Component {
constructor(props){
super(props);
this.state = {value: ''};
}
handleChange=(e)=> {
this.setState({value: e.target.value});
}
handleSubmit= (e)=>{
e.preventDefault();
const value = this.state.value;
this.props.getResult(value)
}
render(){
return (
<form className="search"onSubmit={this.handleSubmit}>
<input type="text" className="search__field" placeholder="Search over 1,000,000 recipes..." value={this.state.value} onChange={this.handleChange}/>
<button className="btn search__btn"></button>
</form>
);
}
}
App component
class App extends Component {
constructor(props) {
super(props);
this.state = {
search:[],
}
}
getResult = async (query) =>{
try {
const key = 'key';
const res = await axios(`https://www.food2fork.com/api/search?key=${key}&q=${query}`);
const result = res.data.recipes;
console.log(result)
// if(result) this.setState({search: result});
}
catch (err) {
alert(err);
}
}
render() {
return (
<div className="App">
<div className="container">
<Header getResult={this.getResult}/>
</div>
</div>
);
}
}
Header
class Header extends Component {
constructor(props) {
super(props);
this.getResult = this.props.getResult;
}
render(){
return (
<div className="header">
<Logo />
<Form getResult={this.getResult}/>
<Like />
</div>
)
}
}

Related

Cannot Read Property 'state' of undefined for this.state

I'm new to react and I was wonder why I keep getting this cannot read property value of "undefined" error. When i consolelog the this.state.username and this.state.todoList I can see that states updating but only after I clicked the submit button it gives me the error.
Any suggestions? Thank you!
import React, { Component } from "react";
import axios from "axios";
export default class TestNote extends Component {
constructor(props) {
super(props);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.onChangeTodoList = this.onChangeTodoList.bind(this);
this.state = {
username: "",
todoList: "",
};
}
onChangeUsername(e) {
this.setState({
username: e.target.value,
});
console.log(this.state.username);
}
onChangeTodoList(e) {
this.setState({
todoList: e.target.value,
});
}
onSubmit(e) {
e.preventDefault();
const todoList = {
username: this.state.username,
todoList: this.state.todoList,
};
console.log(todoList);
axios
.post("http://localhost:5000/list/add", todoList)
.then((res) => console.log(res.data));
}
render() {
return (
<div className="container">
<form>
<label>Username: </label>
<input
type="text"
required
value={this.state.username}
onChange={this.onChangeUsername}
/>
<label>TodoList: </label>
<input
type="text"
value={this.state.todoList}
onChange={this.onChangeTodoList}
/>
<input type="submit" value="Add This List" onClick={this.onSubmit} />
</form>
</div>
);
}
}
Your onSubmit are losing this context. You should try one of these methods, but I recommend you using arrow function.
You should .bind(this) to onSubmit
...
<input type="submit" value="Add This List" onClick={this.onSubmit.bind(this)} />
...
Defined onSubmit as an arrow function
...
onSubmit = (e) => {
e.preventDefault();
const todoList = {
username: this.state.username,
todoList: this.state.todoList,
};
}
...
You did not bind onSubmit function/method to this, therefore this has no context and would return undefined. You can bind this to onSubmit in the constructor to fix the error.
Another way to avoid having to bind this is to use arrow functions for your class methods/functions as they automatically bind this to that function, you won't have to worry about doing it yourself.
constructor(props) {
super(props);
this.state = {
username: "",
todoList: ""
};
// this.onChangeUsername = this.onChangeUsername.bind(this);
// this.onChangeTodoList = this.onChangeTodoList.bind(this);
// this.onSubmit = this.onSubmit.bind(this);
}
onChangeUsername = e => {
this.setState({
username: e.target.value
});
console.log(this.state.username);
};
onChangeTodoList = e => {
this.setState({
todoList: e.target.value
});
};
onSubmit = (e) => {
e.preventDefault();
const todoList = {
username: this.state.username,
todoList: this.state.todoList
};
When you define function in your component without using arrow function you have to bind to each function the this keyword in the constructor for that function to consider each reference of this in if definition to refer to the created component.
Here is the definition of the bind method
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
So if you have component Home define like this
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Jeanne Doe"
}
}
handleChangeName () {
this.setState({name: "John Doe"});
}
render() {
return <div>
<h2>Welcome {this.state.name}</h2>
<button onClick={this.handleChangeName}>Change name</button>
</div>
}
}
ReactDOM.render(<Home />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Here you get the error that tell you "TypeError: this is undefined"
So to fix that you have to use bind like this
class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
name: "Jeanne Doe"
}
this.handleChangeName = this.handleChangeName.bind(this);
}
handleChangeName () {
this.setState({name: "John Doe"});
}
render() {
return <div>
<h2>Welcome {this.state.name}</h2>
<button onClick={this.handleChangeName}>Change name</button>
</div>
}
}
ReactDOM.render(<Home />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Or to completely avoid the use of bind as you can have so many methods define in your component you can use arrow function which look like this
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Jeanne Doe"
}
}
const handleChangeName = () => {
this.setState({name: "John Doe"});
}
render() {
return <div>
<h2>Welcome {this.state.name}</h2>
<button onClick={this.handleChangeName}>Change name</button>
</div>
}
}

Reset State on Props Change

So, I have three components(Search, Pages, HanddlesApi) plus App.js and I'm passing props around via functions to other child components with no problem.
The Search component passes it's state of userInput to HandleApi component and updates the api using componentDidUpdate. (this works great, np here).
I added the Pages component to update the api's page number so that the user can cycle through pages of content. This works, but with issues. The user can do a search and cycle though the pages, but if they enter a new query, they will land  on the same page number of the new query. For example, If
I searched "ducks" and clicked to the next page(2). Then did a search for "dogs" they would land on page two of "dogs"
So my question is how do I reset state for my Pages component only when a user enters a new query?
I saw that componentWillReceiveProps is being deprecated, so I can't use that.
getDerivedStateFromProps  seemed like it might be a good idea, but from what I read it should only be used in rare cases.
So, the two most likely options seemed to be, use componentDidUpdate in a way I don't understand or use key?
Overall I'm just confused on what to do
In my HanddlesApi Component I'm passing the follwoing into the API:
q: this.props.inputValue ? this.props.inputValue : 'news',
page: this.props.pageNum ? this.props.pageNum: 0
then..
componentDidMount() {
this.fetchNews()
}
componentDidUpdate(prevProps, prevState) {
if (this.props.inputValue !== prevProps.inputValue || this.props.pageNum !== prevProps.pageNum) {
this.setState({
news: []
}, this.fetchNews);
}
}
Then in my Pages Component, I have
import React, { Component } from 'react'
class Pages extends Component {
constructor(props) {
super(props)
this.state = {
nextPage: 1,
prevPage: 0
}
}
handleNextClick = () => {
this.setState({
nextPage: this.state.nextPage + 1,
})
}
handlePrevClick = () => {
this.setState({
prevPage: this.state.prevPage - 1,
})
}
render() {
return (
<div className='pageNav'>
<button className="PrevButton" onClick={() => {
this.handlePrevClick()
this.props.onNextButtonClick(this.state.prevPage)
}}>Previous </button>
<button className="nextButton" onClick={() => {
this.handleNextClick()
this.props.onNextButtonClick(this.state.nextPage)
}}>Next </button>
</div>
)
}
}
export default Pages
Search Component
import React, { Component } from 'react';
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: ""
}
}
handleChange = (e) => {
this.setState({
inputValue: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.onSubmittedSearch(this.state.inputValue)
}
render() {
//{this.props.onSubmittedSearch(this.state.inputValue)}
return (
<section>
<form onSubmit={this.handleSubmit}>
<label htmlFor="searching"></label>
<input type="text" placeholder="Search Something" value={this.state.inputValue} onChange={this.handleChange} />
<button type="submit">Search </button>
</form>
</section>
)
}
}
export default SearchBar
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: null,
pageNum: 1
}
}
// used to pass props from SearchBar to NewsList
onSubmittedSearch = (inputValue) => {
this.setState({
inputValue: inputValue
})
}
onNextButtonClick = (pageNum) => {
this.setState({
pageNum: pageNum
})
}
render() {
return (
<main>
<SearchBar onSubmittedSearch={this.onSubmittedSearch} />
<NewsList inputValue={this.state.inputValue} pageNum={this.state.pageNum} />
<Pages onNextButtonClick={this.onNextButtonClick} />
<Footer />
</main>
)
}
}
export default App;
You should let App in charge of changing and holding the current page number. So you can reset it each time your search component submit. Here is a working exemple:
class Pages extends React.Component {
render() {
return (<div className='pageNav'>
<button disabled={this.props.page <= 1} className="PrevButton" onClick={this.props.onPrevButtonClick}>Previous
</button>
<span>{this.props.page}</span>
<button className="nextButton" onClick={this.props.onNextButtonClick}>Next
</button>
</div>)
}
}
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = {
inputValue: ""
}
}
handleChange = (e) => {
this.setState({inputValue: e.target.value})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.onSubmittedSearch(this.state.inputValue)
}
render() {
//{this.props.onSubmittedSearch(this.state.inputValue)}
return (<section>
<form onSubmit={this.handleSubmit}>
<label htmlFor="searching"></label>
<input type="text" placeholder="Search Something" value={this.state.inputValue} onChange={this.handleChange}/>
<button type="submit">Search
</button>
</form>
</section>)
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
inputValue: null,
pageNum: 1
}
}
// used to pass props from SearchBar to NewsList
onSubmittedSearch = (inputValue) => {
this.setState({inputValue: inputValue, pageNum: 1})
}
onNextButtonClick = () => {
this.setState(state => ({
pageNum: state.pageNum + 1
}))
}
onPrevButtonClick = (pageNum) => {
this.setState(state => ({
pageNum: Math.max(state.pageNum - 1, 1)
}))
}
render() {
return (<main>
<SearchBar onSubmittedSearch={this.onSubmittedSearch}/>
<Pages onNextButtonClick={this.onNextButtonClick} onPrevButtonClick={this.onPrevButtonClick} page={this.state.pageNum}/>
</main>)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

React component: Simulate standard HTML <input/> API

I want to make a component with an API like any standard input element, meaning I want to use it like this: <CustomInput value={this.state.custom_input_state} onChange={this.handleChange} />
Here is what I have so far, but I have no idea how to
Make the custom components value changeable from the parent component
after it has been constructed
Make the parent's onChange handler function recieve a change event when the
custom component's value changes
Here is my test setup:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 0
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.increment = this.increment.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handleSubmit(event) {
alert(this.state.foo);
event.preventDefault();
}
increment() {
this.setState({foo: this.state.foo + 1});
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<div onClick={this.increment}>Increment from parent</div>
<CustomInput name="foo" value={this.state.foo} onChange={this.handleChange}/>
<input type="submit" value="Submit" />
</form>
)
}
}
class CustomInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value,
};
this.increment = this.increment.bind(this);
}
increment() {
this.setState({value: this.state.value + 1});
}
render() {
return(
<React.Fragment>
<div onClick={this.increment}>Increment self</div>
<input name={this.props.name} value={this.state.value}/>
</React.Fragment>
);
}
}
You have to pass all the CustomInput props to the input element. In CustomInput component actually it not recieving the onChange event.
Pass the prop onChange event to input element
Form Component
class Form extends Component {
constructor() {
super();
this.state = {
foo: 'React'
};
}
handleChange = (event) => {
this.setState({
[event.target.name]:event.target.value
})
}
render() {
return (
<div>
<form>
<Custominput name="foo" onChange={this.handleChange} value={this.state.foo} />
</form>
{this.state.foo}
</div>
);
}
}
CustomInput Component
export default class CustomInput extends React.Component{
render(){
return(
<input {...this.props} />
)
}
}
demo link

EventHandler with React Routing

I have a nested route in react with react-router plugin and want to connect the ui with some functionality
class MyLogin extends React.Component {
constructor(props){
super(props);
this.state={
email:'',
password:''
}
}
login() {
alert("OK");
}
}
and the ui
const Login = ({ match }) => {
return (
...
<TextField
hintText="Email eingeben"
type="email"
floatingLabelText="Email"
onChange = {(event,newValue) =>
MyLogin.setState({email:newValue})}
/>
...
<RaisedButton label="Anmelden" primary={true} /*style={style}*/ onClick={ MyLogin.login }/>
...
So how can i get access to state variables and bind the event handler from the button to my own code?
Well, if you want to access another component inside a component you can use ref for that. But refs shouldn't be overused. I provide this example only for educational purposes (you should probably rework your app to follow good patterns).
class MyLogin extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
login() {
alert("OK");
}
render() {
return null;
}
}
const Login = ({ match, MyLoginRef }) => {
if (!MyLoginRef) return null;
return (
<>
<input
type="email"
onChange={event =>
MyLoginRef.setState({ email: event.currentTarget.value })
}
/>
<button label="Anmelden" onClick={MyLoginRef.login} />
</>
);
};
class App extends React.Component {
state = {}
refFn = (el) => (this.setState({el}))
render() {
console.log(this.el);
return (
<div className="App">
<MyLogin ref={this.refFn}/>
<Login MyLoginRef={this.state.el} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
live demo on codesandbox.io https://codesandbox.io/s/kmv6x4j237

How to iterate array of objects with properties in ReactJS?

I have a constructor in my main component:
class App extends Component {
constructor(props){
super(props);
this.state = {
items: []
}
};
render() {
return (
<div className="App">
<ItemList items={this.state.items}/>
<AddItemForm items={this.state.items}/>
</div>
);
}
}
In component AddItemForm I'm adding to array items objects with properties "item_name" that is string and "comment" with data type object. View of component:
class AddItemForm extends React.Component {
constructor(props) {
super(props);
this.state = {
item:{}
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({item:
{
item_name: event.target.value,
comment:{}
}
});
}
handleSubmit(event) {
event.preventDefault();
this.props.items.push(this.state.item);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" item_name={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default AddItemForm;
How can I iterate this array to get all item_name values of every object and display them as list in my ItemList component?
This should help.
class App extends Component {
constructor(props){
super(props);
this.state = {
items: []
}
};
addItemToItemsList = (item) => {
const {items=[]} = this.state;
items.push(item);
this.setState({
items : items
});
}
render() {
return (
<div className="App">
<ItemList items={this.state.items}/>
<AddItemForm
items={this.state.items}
addItemToItemsList={this.addItemToItemsList}
/>
</div>
);
}
}
class ItemList extends React.Component {
render () {
const {items} = this.props;
return (
<div>
{items.map((item, index) => {
return (
<div key={index}>item.item_name</div>
)
})}
</div>
);
}
}
class AddItemForm extends React.Component {
constructor(props) {
super(props);
this.state = {
item: {
item_name : '',
comment:{}
}
};
}
handleChange = (event) => {
const new_item = Object.assign({}, this.state.item, {item_name: event.target.value});
this.setState({
item: new_item
});
}
handleSubmit = (event) => {
event.preventDefault();
this.props.addItemToItemsList(this.state.item);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" item_name={this.state.item.item_name} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default AddItemForm;
I think, you have an error inside AddItemForm, you should pass onSubmit function from App to AddItemForm and change items through this function:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
}
this.handleSubmit = this.handleSubmit.bind(this);
};
handleSubmit(value){
this.setState({
items: this.state.items.concat(value)
})
}
render() {
return (
<div className="App">
<ItemList items={this.state.items} />
<AddItemForm
onSubmit={this.handleSubmit}
items={this.state.items} />
</div>
);
}
}
About main question, one of the way to solve this problem
const ItemList = ({items}) => (
<div>
{items.map( (item, index)=> (
<div key={index}>{item.item_name}</div>
))}
</div>
);
full working example here: https://codesandbox.io/s/7k624nz94q
You can't add to the array directly. You need to pass a callback that will add to the array in your parent component's state. This is a very common pattern when using react.
Here is a skeleton of what you need to do:
In your parent component, you don't need to pass the whole list to your AddItemForm component, just a addItem callback to your child component:
class App extends Component {
constructor(props){
super(props);
this.state = {
items: []
}
this.addItemToList = this.addItemToList.bind(this);
};
render() {
return (
<div className="App">
<ItemList items={this.state.items}/>
<AddItemForm addItemToList={this.addItemToList}/>
</div>
);
}
addItemToList(newValue) {
// Here you add the item to your state
// Always treat your state as immutable, so create a copy then add the item, then set your new State
const newArray = this.state.items.slice(); // clone
newArray .push(newValue); // Add value
this.setState({items: newArray}); // Set the new state
}
}
More info on how to add items to an array in the state here: React.js - What is the best way to add a value to an array in state
Then you use that callback in your child component:
handleSubmit(event) {
event.preventDefault();
// this.props.items.push(this.state.item);
// Here don't mutate the props, instead call the callback to add the item to your parent's component's state
this.props.addItemToList(this.state.item);
}
To display a list of items, you need to use the map function: https://reactjs.org/docs/lists-and-keys.html#rendering-multiple-components

Resources