How to get data from props.children to its parent in ReactJS? - javascript

I'm trying to get data from a props.children[Input] to it's parent[Form], to build an all in one form component. I am currently stuck at getting data to Input component to Form component.
This is what i have done so far =>
edit.js =>
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {
formFields: {
name: '',
email: ''
}
}
}
render() {
return (
<Form
id={ this.props.match.params.id }
onReceiveFormData={ this.onFiledDataChange.bind(this) }
>
<Input
label='name'
name='name'
value='some'
/>
<Input
label='email'
name='email'
value='email#gmail'
/>
</Form>
);
}
}
Input.js =>
export default class Input extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value
}
}
static setValue(val) {
return val ? val : ''
}
handleInputChange = (event) => {
this.setState({
value : event.target.value
})
this.props.onInputChange({
[event.target.name] : event.target.value
})
}
render() {
return (
<input
name={ this.props.name }
value={ Input.setValue(this.state.value) }
/>
);
}
}
Form.js =>
export default class Form extends Component {
constructor(props) {
super(props)
}
saveClick() {
/**
Save logic => I want to get the field data here
**/
}
render() {
return (<div>
{
this.props.children
}
<Button
onClick={ () => this.saveClick() }
>Save</Button>
</div>
);
}
}
For example, If post -> edit page and user -> edit page =>
I would like to do setting the input value inside the Form.js, Form.js will be responsible for setting the values inside those Inputs when it is mounted and it will be responsible for sending the data to the server.
I would like to the know, the scenario I have suggest is possible or is there a better way to do it?

Invert control (make Input a controlled component). Keeping state inside components unnecessarily is generally a bad design practice. Look at the existing <input> it takes a value and an onChange. Your component should do too.
const Input = ({
className,
...rest
}) => (
<input
className={classNames('Input', className)}
type="text"
{...rest}
/>
);
Then the parent has the state for when the form is submitted in Edit.
onFormSubmit = e => {
e.preventDefault();
console.log(this.state.name, this.state.email);
}
onChange = name => e => this.setState({ [name]: e.target.value })
// ...
<Input onChange={this.onChange('name')} value={this.state.name} />
<Input onChange={this.onChange('email')} value={this.state.email} />
If you want Form to listen in on state changes you could do this:
onChange = (e, originalOnChange) => {
console.log('onChange called:', e.target.value);
originalOnChange && originalOnChange(e);
}
// ...
const children = React.Children.map(this.props.children, child =>
React.cloneElement(child, { onChange: e => this.onChange(e, child.props.onChange) })
);
It's a bit unorthodox but technically achievable.

Related

I want to control value of input in parent component (Child is PureComponent)

Before question, sorry for massy code because I'm newbie at code.
I made Input component by PureComponent.
But I have no idea how to control(like change state) & submit its value in Parent component.
this is Input PureComponent I made:
index.jsx
class Input extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: props.value,
name: props.value,
};
this.setRef = this.setRef.bind(this);
this.handleChange = this.handleChange.bind(this);
}
setRef(ref) {
this.ref = ref;
}
handleChange(e) {
const { name, onChange, type } = this.props;
onChange(name, e.target.value);
this.setState({ isInputError: checkError, value: e.target.value });
}
render() {
const { label, name, type} = this.props;
return (
<div className="inputBox">
<input
id={name}
value={this.state.value}
type={type}
ref={this.setRef}
onChange={this.handleChange}
className="BlueInput"
/>
<label>{label}</label>
</div>
);
}
}
Input.propTypes = {
label: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
type: PropTypes.oneOf(["text", "password", "number", "price", "tel"]),
onChange: PropTypes.func,
}
Input.defaultProps = {
value: "",
type: "text",
onChange: () => { },
}
export default Input;
And this is Parent component where I want to control value
Join.js
function Join() {
const [idMessage, setIdMessage] = useState("");
const [isId, setIsId] = useState(false);
const onChangeId = (name, value) => {
const currentId = value;
const idRegExp = /^[a-zA-z0-9]{4,12}$/;
if (!idRegExp.test(currentId)) {
setIdMessage("Wrong format");
setIsId(false);
} else {
setIdMessage("You can use this");
setIsId(true);
}
}
const handleSubmit = (e) => {
e.preventDefault();
let formData = new FormData();
console.log(formData);
alert("Join completed.");
}
return (
<div className="join-wrap">
<form encType="multipart/form-data" onSubmit={handleSubmit}>
<Input name="id" label="ID" onChange={onChangeId} />
<p className={isId ? 'possible' : 'impossible'}>{idMessage}</p>
<button type="submit">Join</button>
</form>
</div>
)
}
export default Join;
How can I control or submit value?
For anyone landing here with a similar question, I would advise you to use functional components. For the custom input component, you do not really need to define any state in the custom component. You can pass the state (value of input) as well as the change handler as prop to the child component. In case you need refs, use forwardRef.
Intentionally, I am not spoon-feeding here. I am just giving food for thought and some keyword to do a quick research and enhance your skills.
index.js
handleChange(e, fieldName) {
this.props.setState({...this.props.state, [fieldName]:e.target.value})
}
-----------------------
<input
id={this.props.name}
value={this.props.state[name]}
type={type} // you can also add type in state
onChange={(e)=>this.handleChange(e, name)}
className="BlueInput"
/>
join.js
const [state,setState] = useState({fName:'',lNAme:''})
{Object.keys(state).map((value)=>{
return <Input name={value} label="ID" state={state} setState = {setState}/>
})}

Input doesn't work when used with debounce, event.persist() and storing value at parent component

I need an input field with debounced search and value should be passed from parent component. But it doesn't work when value passed from parent component. What is the right way to implement it?
Codesandbox example https://codesandbox.io/embed/debounce-input-owdwj
Text field with debouncing
class MyTextField extends Component {
search = _.debounce(event => {
this.props.onChange(event.target.value);
}, 300);
handleChangeInput = event => {
event.persist();
this.search(event);
};
render() {
return (
<TextField
value={this.props.value}
placeholder="type"
onChange={this.handleChangeInput}
/>
);
}
}
Parent component storing the value of text field
class Form extends Component {
state = {
value: ""
};
handleChangeInput = value => {
this.setState({
value
});
};
render() {
return (
<div>
<h2>{this.state.value}</h2>
<MyTextField
value={this.state.value}
onChange={this.handleChangeInput}
/>
</div>
);
}
}
The problem here is that you are updating the component only after 300 seconds which will not update the input box also. first, you need to update the search box component whenever there is a keyup and later parent can be informed about the changed after 300 seconds
I have updated your code reference please check it out https://codesandbox.io/embed/debounce-input-gm50t
Declare your debounce function in componentDidMount is everything will be fine.
1) Without state
class MyTextField extends Component {
handleChangeInput = e => {
this.search(e.target.value)
};
componentDidMount() {
this.search =_.debounce((value) => {
this.props.onChange(value);
}, 300)
}
render() {
return (
<TextField
value={this.props.value}
placeholder="type"
onChange={this.handleChangeInput}
/>
);
}
}
export default MyTextField;
2) With state:
class MyTextField extends Component {
state = {
textValue: ''
}
handleChangeInput = e => {
this.setState({
textValue: e.target.value
}, () => { this.search()})
};
componentDidMount() {
this.search =_.debounce(() => {
this.props.onChange(this.state.textValue);
}, 300)
}
render() {
return (
<TextField
value={this.props.value}
placeholder="type"
onChange={this.handleChangeInput}
/>
);
}
}
export default MyTextField;
Hope that helps!!!

React form sending empty object

I am trying to send user input in a form component to the component managing the state. I tried to use a callback but my input is not being sent.
Tried to use the form object in a callback
//here is the form component
class ListForm extends React.Component {
constructor(props) {
super(props);
this.state = {
NewItem: {
itemText: "",
id: Date.now(),
completed: false
}
};
}
handleChanges = e => {
this.setState({...this.state,
NewItem: { ...this.state.NewItem, [e.target.name]: e.target.value }
});
};
submitItem = e => {
this.setState({ itemText: "" });
this.props.addItem(e, this.state.NewItem);
};
render() {
return (
<form onSubmit={this.submitItem}>
<input
type="text"
value={this.state.NewItem.itemText}
name="itemText"
onChange={this.handleChanges}
/>
<button>Add</button>
</form>
);
}
}
export default ListForm;
addItem is in the parent component and looks like this. Groceries is just an array of objects with itemText, id and completed
addItem = (e, item) => {
e.preventDefault();
this.setState({
...this.state,
groceries: [...this.state.groceries, item]
});
};
I can enter in the form, but when I hit enter, nothing is added to the list
Very first thing to try is to add e.persist to your addItem() function
addItem = (e, item) => {
e.preventDefault();
e.persist()
this.setState({
...this.state,
groceries: [...this.state.groceries, item]
});
};
Another option is using redux to save the form data in the Global state and then accessing it in the parent component through redux. Here is an example of what that might look like:
import React, { Component } from 'react';
import * as ACTIONS from '../store/actions/actions';
import { connect } from 'react-redux';
class Form1 extends Component {
state ={
value: ''
}
handleChange = (event) => (
this.setState({value: event.target.value})
)
handleSubmit = (event) => {
event.preventDefault()
this.props.input_action_creator(event.target.name.value)
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input id="name" onChange={this.handleChange} type="text" />
<button type="submit"> Submit </button>
</form>
<br />
</div>
)}
}
function mapDispatchToProps(dispatch) {
return {
input_action_creator: (text) => dispatch(ACTIONS.user_input(text))
}
}
export default connect(mapDispatchToProps)(Form1);
class Parent extends Component {
access form data with
this.props.user_Text
}
function mapStateToProps(state) {
return {
user_text: state.user_reducer.user_text
}
}
here is a fully funcitoning react-redux project if you are interested
https://github.com/iqbal125/modern-react-app-sample

Clear input in stateless React component

I want to implement an X icon inside Input component that will clear the input field. I can easily do it if I control the state. But is it actually possible with stateless component?
I use react-semantic-ui, their stateful components have auto controlled state.
So I want to create an input that can be used like this:
//Controlled
class App extends React.Component {
state = {
value:''
}
onChange = (event, props) => {
this.setState({value: props.value});
}
onClearInput = () => {
this.setState({value: ''});
}
render() {
return (
<MyInput
clearable
value={this.state.value}
onChange={this.onChange}
onClearInput={this.onClearInput}
/>
)
}
}
Or
// Uncontrolled
class App extends React.Component {
onChange = (event, props) => {
doSomething(props.value);
}
render() {
return (
<MyInput
clearable
onChange={this.onChange}
/>
)
}
}
In the second example, clearable feature will not work because we're not controlling the value.
MyInput can be implemented like this:
import React from 'react';
import { Input } from 'semantic-ui-react';
import ClearIcon from './ClearIcon';
function MyInput(props) {
const prepareProps = {...props};
if (props.clearable) {
prepareProps.icon=<ClearIcon onClick={props.onClearInput} />;
delete prepareProps.clearable;
}
delete prepareProps.onClearInput;
return (
<div className="my-input">
<Input {...prepareProps} />
</div>
);
}
...etc.
My problems:
clearable feature must work in both controlled and uncontrolled manner.
clearable feature should not require a handler. It would be nice to just provide a prop and handle the render and behavior of the X button under the hood.
I don't see any way to make this work. Any ideas?
Allowing the user of your component to set the value via props and still being able to clear the input can be easily achieved, e.g. like this:
class MyInput extends React.Component {
constructor(props) {
super(props);
this.state = {value: props.value || ''};
}
handleChange = event => {
const { onChange } = this.props;
this.setState({ value: event.currentTarget.value });
onChange && onChange(event);
};
handleClear = () => {
const { onClearInput } = this.props;
this.setState({ value: "" });
onClearInput && onClearInput();
};
render() {
const { value } = this.state;
const { clearable, onChange, ...inputProps } = this.props;
const clearIcon = clearable && <ClearIcon onClick={this.handleClear} />;
return (
<div className="my-input">
<Input value={value} icon={clearIcon} onChange={this.handleChange} {...inputProps} />
</div>
);
}
}
You could even make it more composable by using an hoc or render props as proposed by #pkuzhel.
Look at this codesandbox example to see it in action.
#Andrey
Would you try this below code? and let me know if that resolves your issue.
import React, { Component } from 'react';
import { Input, Button } from 'semantic-ui-react'
class App extends Component {
clear = () => {
console.log(this.inputRef.target.value);
this.inputRef.target.value = '';
}
render() {
return (
<div className="App">
<Input placeholder='Search...' onChange={(input) => {input.persist(); this.inputRef = input}} />
<Button onClick={this.clear}>Clear</Button>
</div>
);
}
}

How to add to state array in React

I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
}
//The function triggered by button which sends the task the user has entered to the tasks array state:
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein', tasks:
this.state.inputText
})
}
render() {
//Where User enters task:
return (
<div className="App">
<main>
<div className="enterTask">
<input type="text" className="inputclass" id="textfield1"
placeholder='Enter a task.'
onChange={event => this.setState({
inputText: event.target.value })}
onKeyPress={event => {
if(event.key === 'Enter') {
this.addItem();
}
}}
/>
<br />
<br />
<button className="button"
onClick={() => this.addItem()} data-
toggle='fadein' data-target='list'>+
</button>
</div>
<!-- Where tasks will appear: -->
<div className="log">
<p className='list'>
<span class={this.state.triggerAnimation}>
{this.state.tasks}
</span>
</p>
<button className="button">-</button>
</div>
</main>
</div>
)
}
}
export default App;
However I don't know how to update the tasks state (which is an array) to push the new tasks.
Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:
this.setState(prevState => ({
tasks: [...prevState.tasks, newTask]
}));
Seems like what you want is this..
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein',
tasks: this.state.tasks.concat(this.state.inputText)})
}
You can use .concat method to create copy of your array with new data:
addTask() {
this.setState({tasks: this.state.tasks.concat(["new value"])})
}
You also need to bind this to addTask in your constructor:
this.addTask = this.addTask.bind(this)
See my example:
https://jsfiddle.net/69z2wepo/103069/
Documentation: https://reactjs.org/docs/react-component.html#setstate
try this
import React from 'react';
class Todo extends React.Component {
constructor(props) {
super();
this.state = {
value: '',
items: []
}
}
onChange = e => this.setState({ value: e.target.value })
onEnter = e => {
if(e.charCode !== 13) return;
this.addItem();
};
onClick = e => {
this.addItem()
};
addItem = () => {
const { value } = this.state;
if(!!value.trim()) return;
this.setState(prev => ({ items: [...prev.items, value], value: '' }))
};
render() {
const { value } = this.state
return (
<div>
<div>
<input
type="text"
value={value}
name="abc"
onChange={this.onChange}
onKeyPress={this.onEnter}
/>
</div>
<button onClick={this.onClick}>Add</button>
</div>
)
}
}
FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.
setState({tasks:this.state.tasks.concat([this.state.inputText])})
Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.
Edit actually put the right code here now...
With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyPress = this.onInputKeyPress.bind(this);
this.addItem = this.addItem.bind(this);
}
onInputChange(e) {
this.setState({ inputText: e.target.value });
}
onInputKeyPress(e) {
if (e.key === "Enter") {
this.addItem();
}
}
addItem() {
const itemToAdd = this.state.inputText;
const tasks = this.state.tasks;
this.setState({
inputText: "",
tasks: tasks.concat(itemToAdd);
});
}
render() {
const { inputText } = this.state;
return(
<div>
<input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.'
value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
<br />
<br />
<button className="button" onClick={this.addItem} data-
toggle='fadein' data-target='list'>+</button>
</div>
);
}
}
Notice how input state is controlled via component state

Categories

Resources