Passing Multiple Refs from Child Component to Parent Component-Reactjs - javascript

I have two components, the Edit(child) and the Index(parent).
There are three input boxes in the Edit component and I'll like to:
Pass these three refs to the Index component
Compare these inputs gotten via refs (in the HandleUpdate function specifically)
Edit.js:
<form onSubmit={props.handleUpdate}>
<input
className=""
name="name"
ref={props.setRef}
onChange={props.handleChange}
defaultValue= {props.name} />
<input
className=""
type="number"
name="day"
min="1"
max="31"
ref={props.setRef}
onChange={props.handleChange}
defaultValue= {props.day} />
<input
className=""
name="dob"
type="month"
ref={props.setRef}
onChange={props.handleChange}
defaultValue={props.dob} />
Index.js:
class BirthdaylistKeeper extends React.Component{
constructor(props){
super(props);
//state
}
...
handleUpdate(event){
event.preventDefault();
//if((name.value === "") && (dob.value === "") && (day.value === "")){
// console.log("empty");
//}
const item = this.state.currentItem;
let index = this.state.items.indexOf(item);
const newItemList = [...this.state.items];
newItemList.splice(index, 1, this.state.dataEdited);
this.setState({
items: [...newItemList],
toggle: false
});
}
//...
render(){
return(
...
<Entry
name={this.state.name}
day={this.state.day}
dob={this.state.dob}
onChange={this.handleChange}
onSubmit={this.handleSubmit}
setRef={this.setRef} />
)
}
How can I achieve this?

I have an idea, instead of passing refs from child to parent, make the refs in the parent and pass them to the child component and then assign them to each input element, something like the following codes:
The parent component:
import React, { Component, createRef } from 'react';
class BirthdaylistKeeper extends Component{
constructor(props) {
super(props);
this.nameRef = createRef();
this.dayRef = createRef();
this.dobRef = createRef();
//state
}
~~~
render() {
return(
~~~
<Entry
nameRef={this.nameRef}
dayRef={this.dayRef}
dobRef={this.dobRef}
}
}
And in the child component pass each ref to the related input element:
<form onSubmit={props.handleUpdate}>
<input
~~~
name="name"
~~~
ref={props.nameRef}
~~~
/>
<input
~~~
name="day"
~~~
ref={props.dayRef}
~~~
/>
<input
~~~
name="dob"
~~~
ref={props.dobRef}
~~~
/>
Also, remember you should use separated refs for each input elements, not using one to all of them.

Related

Implement recursive onClick event in react js

I am trying to implement a recursive method on reactjs, but only when data is submitted or clicked.
Following is the code I am trying with:
import React, { Component } from "react";
class Textbox extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {this.setState({value: event.target.value}); }
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="React" />
</div>
</form>
);
}
}
export default Textbox;
which generates the following view.
I want to use a recursive method onClick or onSubmit, such that it will generate another text box upon submission. Like
which I want to continue until I click some "Exit" or "Stop" button, which I can add to the top of the view.
From what I have read about recursive implementation on ReactJS, I need to call the class/function again inside render. When I do that I think react is getting inside the infinite loop and freezes the browser.
what I tried is to call Textbox inside the <div> <div/> of the render method. Like this:
.... other code lines are same
<div>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="React" />
<Textbox/>
</div>
How can I generate a recursive textbox on submission/clicking event on the previous text box?
You could do it like this, where showNextInput prevents the infinite loop:
class Textbox extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
showNextInput: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {this.setState({value: event.target.value}); }
handleSubmit(event) {
console.log('submit');
event.preventDefault();
this.setState({ showNextInput: true });
}
render() {
return (
<>
<form onSubmit={this.handleSubmit}>
<div>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</div>
</form>
{ this.state.showNextInput ? <Textbox/> : null }
</>
);
}
}
However, your use case looks like something you would usually do by
managing a list of values somewhere,
add items as required inside your handlers, and
then display a list of these items
Here a quick and dirty example:
export class TextboxList extends React.Component {
constructor(props) {
super(props);
this.state = {
values: {
0: ''
},
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(index, value) {
this.setState({
values: {
...this.state.values,
[index]: value
}
});
}
handleSubmit(event) {
event.preventDefault();
this.setState({
values: {
...this.state.values,
[Object.keys(this.state.values).length]: '' // add new empty item to list
}
});
}
render() {
return (
<>
<form onSubmit={this.handleSubmit}>
{ Object.keys(this.state.values).map( (index) => {
const value = this.state.values[index];
return <div key={ index }>
<label>
Name:
<input
type="text"
value={ value }
onChange={ (event) => this.handleChange( index, event.target.value ) }
/>
</label>
<input type="submit" value="Submit" />
</div>;
})}
</form>
</>
);
}
}
export default Textbox;

Issue with unique key props for child elements

I have a generic Todo List built in React. Each task from user input is stored in tasks array declared in parent component <ReactTodoApp />. Tasks are rendered in child component <TodoList />. A unique key is assigned to each task in DOM element <label />. When inspecting dev tools unique ids are generating, however error is still present.
Would anyone know why I am still getting the "unique key prop" error?
Link to working application: https://codesandbox.io/s/todo-list-34udn?file=/src/App.js
JS file:
import React, { Component } from "react";
import "./styles.css";
export default class ReactTodoApp extends Component {
constructor(props) {
super(props);
this.state = {
//container for new task
input: "",
tasks: []
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
handleChange(event) {
this.setState({ input: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
//condition for empty empty
if (!this.state.input) {
return;
}
//declare object to store
const newTask = {
input: this.state.input,
id: 1 + Math.random()
};
//request update to current tasks state
this.setState((state) => ({
tasks: state.tasks.concat(newTask),
input: ""
}));
}
//updater function to remove task
handleRemove(props) {
//create new task list
const newTasksList = this.state.tasks;
//remove selected item from new task list
newTasksList.splice(props, 1);
//update state for tasks
this.setState({ tasks: newTasksList });
}
render() {
return (
<div>
<h1>React Todo</h1>
<form onSubmit={this.handleSubmit} className="add-item">
<input
type="text"
value={this.state.input}
onChange={this.handleChange}
className="add-item__input"
placeholder="new item"
/>
<button type="submit" className="submit">
add item
</button>
</form>
<TodoList tasks={this.state.tasks} handleRemove={this.handleRemove} />
</div>
);
}
}
class TodoList extends React.Component {
render() {
return (
<div className="list-container">
{this.props.tasks.map((task) => (
<label keys={task.id} className="item-container">
<input type="checkbox" />
<p className="item__text">{task.input}</p>
<button onClick={this.props.handleRemove} className="remove-button">
x
</button>
<span className="custom-checkbox" />
</label>
))}
</div>
);
}
}
Just change keys={task.id} to key={task.id}

setState updating state but not rerender

I have a react function to handle a form submit
handleSubmit(e){
this.setState({size: this.state.value});
this.setState({maze : <Maze size={this.state.value}></Maze>}, () => this.forceUpdate());
console.log('set state finished');
e.preventDefault();
}
in the app class which has the following
render() {
return (
<div className="App">
<form onSubmit={this.handleSubmit}>
<label>Size: </label>
<input type="number" min="3" step="1" onChange={this.handleChange} value={this.state.value}></input>
<input type="submit" value="Generate Maze"></input>
</form>
{this.state.maze}
</div>
);
}
Yet the code does not rerender the Maze;
2 things to note
I put an alert in the setState callback and it popped up (meaning, I assume, that setState finished and there should have been a rerender) and passed without a rerender
the force update callback doesn't work
How would I fix this?
Complete code of the Main class
class App extends Component {
constructor(props){
super(props);
this.state = {
size : 20,
value : 20,
showMaze : false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount(){
this.setState({maze : <Maze size={this.state.size}></Maze>});
}
handleChange(e){
this.setState({value : e.target.value});
}
handleSubmit(e){
this.setState({size: this.state.value});
this.setState({showMaze: false});
this.setState({maze : <Maze size={this.state.value}></Maze>}, () => this.forceUpdate());
this.setState({showMaze: true});
e.preventDefault();
}
render() {
return (
<div className="App">
<form onSubmit={this.handleSubmit}>
<label>Size: </label>
<input type="number" min="3" step="1" onChange={this.handleChange} value={this.state.value}></input>
<input type="submit" value="Generate Maze"></input>
</form>
{this.state.showMaze? this.state.maze : null}
</div>
);
}
}
complete code of the Maze class (minus the generation algorithm)
class Maze extends Component{
constructor(props){
super(props);
this.state = {
size : this.props.size,
maze : null
}; //declare the maze null for now
}
componentDidMount(){
this.generateMazev2(this.props.size);
}
generateMazev2 (size){
//snippped
}
render(){
var renderMaze = new Array(); //store the table
//calculate the percentage size of each Cell
var size = 80.0 / this.props.size; //the table should ideally take up 80% of the page
if(this.state.maze !== null){
this.state.maze.forEach(function (row, index, arr){
var newRow = new Array();
row.forEach(function (box, theIndex, arr){
newRow[theIndex] = <Cell size={size} top={box.top} bottom={box.bottom} right={box.right} left={box.left}></Cell>;
});
renderMaze[index] = <tr>{newRow}</tr>;
});
return (<table>{renderMaze}</table>);
}else{
return (<p>Generating</p>);
}
}
}
This is not the correct way, UI component should not be stored in state value. State should contain only the data.
Solution:
Maintain a bool in state variable and use that bool for conditional rendering of Maze component, initial value of bool will be false and update the bool inside handleSubmit function.
Like this:
handleSubmit(e){
e.preventDefault();
this.setState({size: this.state.value, showMazeCom: true});
}
render() {
return (
<div className="App">
<form onSubmit={this.handleSubmit}>
<label>Size: </label>
<input type="number" min="3" step="1" onChange={this.handleChange} value={this.state.value}></input>
<input type="submit" value="Generate Maze"></input>
</form>
{this.state.showMazeCom && <Maze size={this.state.value}></Maze>}
</div>
);
}
Update:
To avoid the unnecessary re-rending of Maze component, use shouldComponentUpdate lifecycle method, compare the new and old props values, if they are not same then only allow the re-rendering.
Like this:
shouldComponentUpdate(nextProps){
if(nextProps.size != this.props.size)
return true;
return false;
}
One thing you could do is changing the render() method as below.
render() {
return (
<div className="App">
<form onSubmit={this.handleSubmit}>
<label>Size: </label>
<input type="number" min="3" step="1" onChange={this.handleChange} value={this.state.value}></input>
<input type="submit" value="Generate Maze"></input>
</form>
{this.state.showMaze? <Maze size={this.state.value}></Maze> : null}
</div>
);
}
After that modify handleSubmit() method as below
handleSubmit(e){
this.setState({size: this.state.value, showMaze: true});
console.log('set state finished');
e.preventDefault();
}
UPDATE
It is not a good practice to keep the components as state properties. Hence, replace the {this.state.showMaze? this.state.maze : null} line in render method as I've shown.
Further, you need to implement componentWillReceiveProps() method in the Maze component and modify its state according to new props.
componentWillReceiveProps(nextProps) {
this.setState({size: nextProps.size})
}

How to get state of a child component in react js ?

I have a Login Component and Input Components :
class Login extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeInput = this.handleChangeInput.bind(this);
this.state = {
email_user: "LOL",
password_user: "",
};
}
handleSubmit(e) {
//alert('A name was submitted: ');
e.preventDefault();
console.log(this.state);
}
handleChangeInput(e){
console.log(e.target.value);
this.setState({value: e.target.value});
}
render() {
return (
<Input size="L" type="email" name="email_user" placeholder="Email" value={this.state.email_user} />
<Input size="L" type="password" name="password_user" placeholder="Mot de passe" value={this.state.password_user} onChange={this.handleChangeInput} /> );
}
}
My Input component returns some HTML like :
<div className={classInput}>
<span className="input__text">
<input type={this.props.type} name={this.props.name} placeholder={this.props.placeholder} defaultValue={this.props.value} onChange={this.handleChange} required />
</span>
</div>
My Input state is updated great by the handleChange inside the Input Component.
But these state are not visible by the Login Component. With or without handleChangeInput, the states in theLogin Component are not saved.
How the Login Component can access to the update state inside child component please?

Uncaught TypeError: Cannot read property 'value' of undefined in REACT JS

I am creating a login form using REACT JS as front-end and PHP as back-end. I am trying to get input values. Code is as below:
import React, { Component} from 'react';
import ReactDOM from 'react-dom';
import {Button, IconButton} from 'react-toolbox/lib/button';
import Input from 'react-toolbox/lib/input';
export default class Login extends React.Component {
constructor() {
super();
this.state = {email: ''};
this.state = {password: ''};
this.onSubmit = this.onSubmit.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
}
handleEmailChange(e) {
this.setState({email: e.target.value});
}
handlePasswordChange(e) {
this.setState({password: e.target.value});
}
onSubmit() {
fetch('http://xyz/check-login', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: this.state.email,
password: this.state.password,
})
})
}
And form is as below:
<form name="Login">
<Input type="text" name="email" value={this.state.email} placeholder="Email Id" className="form-control" onChange={this.handleEmailChange} />
<Input name="password" value={this.state.password} placeholder="Password" type="password" className="form-control m-b-10" onChange={this.handlePasswordChange} />
<Button type="button" className="m-t-20 orange" label="Sign in " onClick={this.onSubmit} />
</form>
But getting the following error:
Uncaught TypeError: Cannot read property 'value' of undefined
I am using react-toolbox. So, I am using component from https://github.com/react-toolbox/react-toolbox/blob/dev/components/input/Input.js and component from https://github.com/react-toolbox/react-toolbox/blob/dev/components/button/Button.js.
First of all, what are <Input ..../> and <Button .../>? Are they your components or they are only form input fields?
I suppose that they are only form fields, thus they need to be in lower case <input ..../> , <button .../>.
Try to bind your functions inside render, like : this.functionName.bind(this).
This is a working code :
class Test extends React.Component {
constructor(props){
super(props);
this.state = {
email: '',
password: '',
};
}
handleEmailChange(e) {
this.setState({email: e.target.value});
}
handlePasswordChange(e) {
this.setState({password: e.target.value});
}
render(){
return (
<div>
<form name="Login">
<input type="text" name="email" value={this.state.email} placeholder="Email Id" className="form-control" onChange={this.handleEmailChange.bind(this)} />
<input name="password" value={this.state.password} placeholder="Password" type="password" className="form-control m-b-10" onChange={this.handlePasswordChange.bind(this)} />
<button type="button" className="m-t-20 orange" label="Sign in " onClick={this.onSubmit}>Sign in</button>
</form>
</div>
)
}
}
React.render(<Test />, document.getElementById('container'));
Here is the fiddle.
UPDATE
I tested it here :
constructor(props){
super(props);
this.state = {
name: '',
email: ''
}
}
handleChange(name, value){
let state = this.state;
state[name] = value;
this.setState({state});
}
render () {
return (
<section>
<Input type='text' label='Name' name='name' value={this.state.name} onChange={this.handleChange.bind(this, 'name')} maxLength={16} />
<Input type='email' label='Email address' icon='email' value={this.state.email} onChange={this.handleChange.bind(this, 'email')} />
</section>
);
}
I'm not sure how it works, but it pass name and value as params to the handleChange function, thus, you can get value as a second param. You don't need event.
First change your this.state in constructor to single - this.state = {emai:'',password:''}, then try to bind handleEmailChange and handlePasswordChange inside of input instead in constructor, u need to set this direct to input,
UPDATE
or if Input and Button are components, u need implement changeMethod and onChange event in them, and send value back to component Login via callback
HOW IT WORKS -
class Input extends React.Component{
constructor(props){
super(props);
this.state= {
value : this.props.value
}
}
componentWillReceiveProps(nextProps){
this.setState({
value: nextProps.value,
})
}
render(){
return(
<input onChange={this.handleChangeValue.bind(this)} type="text" name={this.props.name} value={this.state.value} placeholder={this.props.placeholder} className={**just class name or send via props too**} />
)
}
handleChangeValue(e){
this.setState({value:e.target.value});
this.props.changeValue(e.target.value);
}
}
class Login extends React.Component{
constructor(props){
super(props);
this.state= {
emailValue : '',
passwordValue: '',
...
}
}
render(){
return(
<div>
<Input type="text" name='email' value={this.state.emailValue} placeholder={'Write email'} className='someName' changeValue={this.changeEmailValue.bind(this)} />
<Input type="text" name='password' value={this.state.passwordValue} placeholder={'Write password'} className='someName' changeValue={this.changePasswordValue.bind(this)} />
</div>
)
}
changeEmailValue(value){
this.setState({emailValue:value});
}
changePasswordValue(value){
this.setState({passwordValue:value});
}
}
Since you are using a custom component <Input/>, it is likely whatever special code that component has is abstracting away the default event passed from the built-in component <input/>. (note the lower case "i") So maybe you need it to read:
handleEmailChange(value) {
this.setState({email: value});
}
handlePasswordChange(value) {
this.setState({password: value});
}
Regardless the fix is likely that onChange is not returning an event, but some other value you were not expecting.
Instead on binding events in the constructor bind it with the input fields, it will solve your problem.
one more suggestion: you are using two state object, Don't use two separate state variable initialisation, define it like this:
this.state = {
email: '',
password: '',
};
If you use Formik, You should set initialValues for it.
<Formik
initialValues={{
email: '',
password: '',
}}
onSubmit={(values) => {
console.log("formik", values)
}}
>

Categories

Resources