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
Related
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>
}
}
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>
I am trying to use Auth0EditProfileWidget
getting error at line 65:
this.form = React.render( <FormControl data={data} onSubmit={onSubmit} />, container );
TypeError: react__WEBPACK_IMPORTED_MODULE_0___default.a.render is not
a function
can anybody help me?
Edited: code
import React from 'react';
import formSerialize from 'form-serialize';
import FieldTypeMapper from './FieldTypes/FieldTypeMapper'
class FormFieldList extends React.Component {
render() {
var fieldNodes = this.props.data.map( data => FieldTypeMapper(data.type)(data) );
return ( <div>{fieldNodes}</div> );
}
}
class ErrorItem extends React.Component {
render() {
return ( <li>{this.props.message}</li> );
}
}
class ErrorControl extends React.Component {
render() {
var errors = this.props.data.map( error => ( <ErrorItem key={error} message={error} /> ) );
var style = {};
if (errors.length === 0) {
style.display = 'none';
}
return ( <ul className="error" style={style}>{errors}</ul> );
}
}
class FormControl extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = props.data;
}
render() {
return (
<form onSubmit={this.handleSubmit} ref="editProfileForm">
<ErrorControl data={this.state.errors} />
<FormFieldList data={this.state.fields} />
<input type="submit" value="Save" />
</form>
);
}
handleSubmit(e) {
e.preventDefault();
var form = this.refs.editProfileForm.getDOMNode();
var data = formSerialize(form, {hash: true});
this.props.onSubmit(data);
}
}
export default class EditProfileForm {
constructor(container, data, onSubmit) {
this.form = React.render( <FormControl data={data} onSubmit={onSubmit} />, container );
}
render(data) {
this.form.setState(data);
}
}
Can you try changing your EditProfileForm class to this:
export default class EditProfileForm {
constructor(props) {
super(props)
}
render() {
return(<FormControl data={this.props.data} onSubmit={this.props.onSubmit} />)
}
}
And then call ReactDom.render() like this:
ReactDOM.render(<EditProfileForm data={dataProp} onSubmit={onSubmitProp} />, container);
export default class EditProfileForm {
constructor(container, data, onSubmit) {
this.form = React.render( <FormControl data={data} onSubmit={onSubmit} />, container );
}
render(data) {
this.form.setState(data);
}
}
things to note here, first, is EditProfileForm a react component or not because EditProfileForm do not extends React.Component
and Second thing is that this.form.setState(data); what is the value of setState why you use this.form.setState() what is it doing.
So I have a component "itemSelection" and inside of it I map through an api response like this
<div className="row">
{this.state.items.map(i => <Item name={i.name} quantity={i.quantity} />)}
</div>
Here the state of "Item" component
constructor(props){
super(props);
this.state = {
visible: false,
selected: false,
}
}
How could I pass the state of "Item" component to "itemSelection" component?
Sending data back up to your parent component should be done by using props.
Fairly common question, see this post for the long answer.
As according to me, If I understood your question you want to call the state of the child component to the parent component.
//Child.js
import s from './Child.css';
class Child extends Component {
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
export default withStyles(s)(Child);
//Parent.js
class Parent extends Component {
render() {
onClick() {
this.refs.child.getAlert()
}
return (
<div>
<Child ref="child" />
<button onClick={this.onClick.bind(this)}>Click</button>
</div>
);
}
}
Also, you can get the code reference from the link: https://github.com/kriasoft/react-starter-kit/issues/909
This a little tricky but Maybe, its help you solving your problem.
//Parent.js
class Parent extends Component {
component(props) {
super(props)
this.state = {
test: 'abc'
}
}
ParentFunction = (value) => {
this.state.test = value;
this.setState(this.state);
}
render() {
return (
<div>
<Child
test={this.state.test}
ParentFunction={this.ParentFunction}
/>
</div>
);
}
}
//Child.js
import s from './Child.css';
class Child extends Component {
component(props) {
super(props)
this.state = {
test: props.test
}
}
handleChange = () => {
this.state.test = event.target.value;
this.setState(this.state);
this.handleOnSave()
}
handleOnSave = () => {
this.props.ParentFunction(this.state.test);
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
</div>
);
}
}
export default withStyles(s)(Child);
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