I tried to make a small program that has an input field where you can insert text. Then when you click save, it saves the text and shows you below. My question is that, initially the text was not showing up after I clicked 'save', but only when I made another change to the input field after clicking 'save'.
After adding this.setState({inputText: ' '}) in saveValue, it started to work but I'm not so sure why.
import React, {Component} from 'react'
import './App.css';
import picture from './picture.jpg'
class App extends Component {
state = {
inputText: '',
savedValues: [],
}
textStorage = (event) => {
this.setState({
inputText: event.target.value
})
}
saveValue = (inputText) => {
this.state.savedValues.push(this.state.inputText)
this.setState({inputText: ' '})
}
render() {
return (
<div className = "App">
<h1>Hello World</h1>
<input
type = "text"
value = {this.state.inputText}
onChange = {(event) => this.textStorage(event)}
/>
<p>Here's your text: {this.state.inputText}</p>
<button onClick = {this.saveValue}>Save</button>
<p>{this.state.savedValues.join('')}</p>
<div>
<img className = "Picture" src={picture} alt="Picture"/>
</div>
</div>
)
}
}
export default App;
Issue
You are mutating your state object when you push a value into it. You aren't returning a new array state reference so react doesn't rerender until you actually update state by updating the input value and trigger the rerender. Adding the update to clear the input value was the state update that triggered a rerender.
saveValue = (inputText) => {
this.state.savedValues.push(this.state.inputText) // <-- mutation!
this.setState({inputText: ' '})
}
Solution
Use a correct state update. array.prototype.concat concatenates a value to and returns a new array reference.
saveValue = () => {
this.setState(prevState => ({
savedValues: prevState.savedValues.concat(prevState.inputText),
inputText: ' ',
}))
}
Alternatively you can use the Spread syntax to create a new array as well.
saveValue = () => {
this.setState(prevState => ({
savedValues: [...prevState.savedValues, prevState.inputText],
inputText: ' ',
}))
}
Related
I'm building a chat app, I have 3 components from parent to child in this hierarchical order: Chat, ChatLine, EditMessage.
I'm looping through messages state in Chat to display multiple ChatLine components as a list, and I pass some state to ChatLine and then to EditMessage.
I need the state :
const [editValue, setEditValue] = useState("");
const [editingId, setEditingId] = useState(null);
to remain in the parent component Chat so I can have access to it later there.
Anyway, now when I click on the Edit button, the EditMessage component shows a textarea, and I'm setting state onChange in it, but everytime I click the Edit button or type a letter in the textarea all the components rerender as I see in React DevTool Profiler, even the children that didn't get affected, I only need the Chat and affected ChatLine to rerender at most.
The whole code is available in CodeSandbox, and deployed in Netlify.
And here it is here also :
(Chat.js)
import { useEffect, useState } from "react";
import ChatLine from "./ChatLine";
const Chat = () => {
const [messages, setMessages] = useState([]);
const [editValue, setEditValue] = useState("");
const [editingId, setEditingId] = useState(null);
useEffect(() => {
setMessages([
{ id: 1, message: "Hello" },
{ id: 2, message: "Hi" },
{ id: 3, message: "Bye" },
{ id: 4, message: "Wait" },
{ id: 5, message: "No" },
{ id: 6, message: "Ok" },
]);
}, []);
return (
<div>
<p>MESSAGES :</p>
{messages.map((line) => (
<ChatLine
key={line.id}
line={line}
editValue={editValue}
setEditValue={setEditValue}
editingId={editingId}
setEditingId={setEditingId}
/>
))}
</div>
);
};
export default Chat;
(ChatLine.js)
import EditMessage from "./EditMessage";
import { memo } from "react";
const ChatLine = ({
line,
editValue,
setEditValue,
editingId,
setEditingId,
}) => {
return (
<div>
{editingId !== line.id ? (
<>
<span>{line.id}: </span>
<span>{line.message}</span>
<button
onClick={() => {
setEditingId(line.id);
setEditValue(line.message);
}}
>
EDIT
</button>
</>
) : (
<EditMessage
editValue={editValue}
setEditValue={setEditValue}
setEditingId={setEditingId}
editingId={editingId}
/>
)}
</div>
);
};
export default memo(ChatLine);
(EditMessage.js)
import { memo } from "react";
const EditMessage = ({ editValue, setEditValue, editingId, setEditingId }) => {
return (
<div>
<textarea
onKeyPress={(e) => {
if (e.key === "Enter") {
// prevent textarea default behaviour (line break on Enter)
e.preventDefault();
// updating message in DB
updateMessage(editValue, setEditValue, editingId, setEditingId);
}
}}
onChange={(e) => setEditValue(e.target.value)}
value={editValue}
autoFocus
/>
<button
onClick={() => {
setEditingId(null);
setEditValue("");
}}
>
CANCEL
</button>
</div>
);
};
export default memo(EditMessage);
const updateMessage = (editValue, setEditValue, editingId, setEditingId) => {
const message = editValue;
const id = editingId;
// resetting state as soon as we press Enter
setEditValue("");
setEditingId(null);
// ajax call to update message in DB using `message` & `id` variables
console.log("updating..");
};
The problem is that all of the child components see their props change any time any of them is in the process of being edited, because you're passing the current editing information to all of the children. Instead, only pass the current editing text (editValue) to the component being edited, not to all the others.
ChatLine doesn't use editValue when it's not the instance being edited. So I'd do one of two things:
Use a different component for display (ChatLine) vs. edit (ChatLineEdit). Almost the entire body of ChatLine is different depending on whether that line is being edited or not anyway. Then only pass editValue to ChatLineEdit.
Pass "" (or similar) as editValue to the one not being edited. In the map in Chat: editValue={line.id === editingId ? editValue : ""}.
Pass an "are equal" function into memo for ChatLine that doesn't care what the value of editValue is if line.id !== editingId. By default, memo does a shallow check of all props, but you can take control of that process by providing a function as the second argument. For instance:
export default memo(ChatLine, (prevProps, nextProps) => {
// "Equal" for rendering purposes?
return (
// Same chat line
prevProps.line === nextProps.line &&
// Same edit value setter (you can leave this out, setters from `useState` never change)
prevProps.setEditValue === prevProps.setEditValue && // ***
// Same editingId
prevProps.editingId === prevProps.editingId &&
// Same editingId setter (you can leave this out too)
prevProps.setEditingId === prevProps.setEditingId && // ***
(
// Same edit value...
prevProps.editValue === prevProps.editValue ||
// OR, we don't care because we're not being edited
nextProps.line.id !== nextProps.editingId
)
);
});
This is fragile, because it's easy to get the check wrong, but it's another option.
I would go for #1. Not even passing props to components that they don't need is (IMHO) the cleanest approach.
I will call the LI tags like this (li) so they are not made into bullet points for his question
Hi I am trying to send a Child component to its Parent in ReactJS.
I tried many things I managed to send the child component state back up to its Parent props but when the page renders I have a couple of (li) tags which I want the update to update with it for example like:
(li) hard coded text (/li)
(li) old text (/li)
(li) update prop (/li)
(li) update prop etc (/li)
but instead the update deletes all previous code so it looks like:
(li) update prop deleted all previous li's (/li)
Hope that made sense here is my code
Parent Component
import React from 'react';
import { generateId, getNewExpirationTime } from '../../util/utilities';
import Thought from '../Thought/Thought.js'
import AddThoughtForm from '../AddThoughtForm/AddThoughtForm.js'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
thoughts: [{
id: generateId(),
text: 'This is a place for your passing thoughts.',
expiresAt: getNewExpirationTime()
},
{
id: generateId(),
text: "They'll be removed after 15 seconds.",
expiresAt: getNewExpirationTime()
}]
};
this.addThought = this.addThought.bind(this);
this.removeThought = this.removeThought.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUpdate = this.componentDidUpdate.bind(this);
}
addThought(thought) {
console.log("LOOK")
console.log(thought)
console.log("DONE")
console.log(this.state.thoughts);
this.state.thoughts.push(thought);
this.setState(prevState => ({
...prevState,
thoughts: [thought]
}))
console.log("passed over")
console.log(this.state.thoughts);
}
removeThought(selected) {
//alert('selected');
let updatedThoughts = this.state.thoughts.filter((thought) => selected.id !== thought.id);
return this.setState({ thoughts: updatedThoughts })
}
render() {
return (
<div className="App">
<header>
<h1>Passing Thoughts</h1>
</header>
<main>
<AddThoughtForm
addThought={this.addThought}
thoughts={this.state.thoughts} />
<ul className="thoughts">
{(this.state.thoughts) && this.state.thoughts.map((thought, index) => (
<Thought
key={thought.id}
thought={thought}
removeThought={this.removeThought} />
))}
</ul>
</main>
</div>
);
}
}
Still on the Parent Component my .addThought(arg) is where the action is. This is where I'm sending the Child AddThoughtForm state object into it. By inside of .addThought() I am doing this:
addThought(thought) {
console.log("LOOK")
console.log(thought)
console.log("DONE")
console.log(this.state.thoughts);
this.state.thoughts.push(thought);
this.setState(prevState => ({
...prevState,
thoughts: [thought]
}))
console.log("passed over")
console.log(this.state.thoughts);
}
What happens is when I pass it over my previous State of my parent is deleted and replaced by this new information from my child component. How do I stop that? I want to only add this new Information to the previous info that the Parent state already have. here is the state from my parent:
constructor(props) {
super(props);
this.state = {
thoughts: [{
id: generateId(),
text: 'This is a place for your passing thoughts.',
expiresAt: getNewExpirationTime()
},
{
id: generateId(),
text: "They'll be removed after 15 seconds.",
expiresAt: getNewExpirationTime()
}]
};
this.addThought = this.addThought.bind(this);
this.removeThought = this.removeThought.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUpdate = this.componentDidUpdate.bind(this);
}
Now over to the Child Component
AddThoughtForm.js
import React from 'react';
import { generateId, getNewExpirationTime } from '../../util/utilities';
class AddThoughtForm extends React.Component {
constructor(props) {
super(props);
this.state = {
ideas: [this.props.thoughts] // I can take off []
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleTextChange(event) {
const { value } = event.target
this.setState(prevState => {
let thoughts = Object.assign({}, prevState.ideas); // creating copy of state variable
thoughts.id = generateId();
thoughts.text = value; // update the name property, assign a new value
thoughts.expiresAt = getNewExpirationTime();
return { thoughts }; // return new object
})
console.log(this.state.ideas)
}
handleSubmit(event) {
event.preventDefault();
this.props.addThought(this.state.thoughts)
alert(this.state.ideas);
}
render() {
return (
<form className="AddThoughtForm" onSubmit={this.handleSubmit}>
<input
type="text"
aria-label="What's on your mind?"
placeholder="What's on your mind?"
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Add" />
</form>
)
}
}
export default AddThoughtForm;
On my .handleTextChange(event) is where I am linking it with my input tag in render so what I am doing what ever Is typed into it I want entered I want it be passed to my Parent Component. well It is passed over but it over-writes the old info every time the old (li) and it is then all just a new li being rendered. Any ideas on how I can fix all of this?
handleTextChange(event) {
const { value } = event.target
console.log(value)
this.setState(prevState => {
let thoughts = Object.assign({}, prevState.ideas); // creating copy of state variable
thoughts.id = generateId();
thoughts.text = value; // update the name property, assign a new value
thoughts.expiresAt = getNewExpirationTime();
return { thoughts }; // return new object
})
console.log(this.state.ideas)
}
I managed to fix it
just had to add on the Parent Component a thoughts: [...prevState, thought] as I was overwritting the old thoughts with the new incoming thought
Like this:
In the method, .addThought()
this.setState(prevState => ({
...prevState,
thoughts: [...prevState.thoughts, thought]
}))
I'm new to React and have to work on a specific assignment where all the logic of my app is in a single parent component and the child component only receives a few props. However, in the child there shouldn't be any logic or almost no logic.
I have a grid (parent component) made of 25 cell and each cell (child component ) can be either on or off. Imagine each cell as a light which is on or off.
From my parent component I'm rendering the cell component 25 times. Every time each cell has:
a key
an id
a status (on or off randomly assigned)
a click event
In my child component when the click event is triggered, the child component return to the parent its id and its status(on or off)
What I want to achieve:
In my parent component I want to be able to detect which child has been clicked and only change the status of the clicked child.
What I get so far:
Despite the parent receive the id and the status of the child that has been clicked, when I change the state via setState, all the children are affected.
Here is a snippet of my parent component:
import React, { Component } from 'react';
import GridSquare from './GridSquare';
import { randomlyLit, cellIdentifier } from '../helpers/helperFuns.js';
let nums = []
cellIdentifier(nums, 25)
class GridContainer extends Component {
static defaultProps = {
gridSize: 25
};
constructor(props) {
super();
this.state = {
cellID: nums,
hasWon: false,
lightStatus: Array.from({ length: 25 }, () => randomlyLit()),
};
this.changeValue = this.changeValue.bind(this);
}
changeValue(id, value) {
console.log(id, value);
this.setState(st => ({
// let result = st.cellID.filter(c => c===id)
// if(result){
// st.value = !value;
// }
lightStatus : !value
})
)
}
render() {
return (
<div>
<h1 className="neon">
Light <span className="flux">Out</span>
</h1>
<div className="GridContainer">
{this.state.cellID.map((el, i) =>(
<GridSquare key={this.state.cellID[i]} id={this.state.cellID[i]} lit={this.state.lightStatus[i]} click={this.changeValue}/>
))}
</div>
</div>
);
}
}
export default GridContainer;
Here is a snippet of my child component:
import React, {Component} from 'react';
class GridSquare extends Component {
constructor(props){
super(props);
this.handleClick= this.handleClick.bind(this)
}
handleClick(){
this.props.click( this.props.id, this.props.lit);
}
render() {
const squareClasses = {
'GridSquareOn': this.props.lit === true,
'GridSquareOff': this.props.lit === false,
'GridSquare': true,
}
function classNames(squareClasses) {
return Object.entries(squareClasses)
.filter(([key, value]) => value)
.map(([key, value]) => key)
.join(' ');
}
const myClassName = classNames(squareClasses)
return(
<div className={myClassName} onClick={this.handleClick}>
</div>
)
}
}
export default GridSquare;
My app.js only renders the parent component and nothing else:
import GridContainer from './components/GridContainer.jsx'
import './style/App.css';
import './style/GridContainer.css';
import './style/GridSquare.css';
function App() {
return (
<div className="App">
<GridContainer />
</div>
);
}
export default App;
Thank you in advance for any help!
changeValue(id, value) {
console.log(id, value);
this.setState(st => ({
// let result = st.cellID.filter(c => c===id)
// if(result){
// st.value = !value;
// }
lightStatus : !value
})
Your change value function is updating the state with the wrong value.
Initial value of your lightStatus is an array of booleans ( assuming randomlyLit function will return a boolean.
When you click on one of the cells, the lightStatus gets updated with the value of false instead of an array.
To fix this, search through the entire lightStatus array by index for which the cell was clicked and update the boolean at that particular index using Array.slice.
How to Optimise
Instead of traversing the whole array every time to update the lightStatus of the cell. You can save the the value in an Object.
What if I could update the status in changeValue like this ?
this.setState((st) => {
return {
...st,
lightStatus: { ...st.lightStatus, [id]: value } // Direct update without traversing through array
}
});
lightStatus can be used to form a "mapping" between cell ids in cellID and their corresponding status booleans.
I am trying to create a autocomplete component. It's an input where user types the countru name and if letters match name of some country, the hints are displayed.
In my App Component i have method handleChange Within this method i change my state two times, which is bad idea.
How can I split it to change state in distinct methods ?
import React, { Component } from 'react';
import AutoComplete from './autoComplete.jsx';
import data from './data.json';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
resoults: []
}
}
handleChange() {
let inputValue = this.refs.input.value;
this.setState({
inputValue: inputValue
});
let regular = "^" + this.state.inputValue;
let reg = new RegExp(regular , "i");
let filtered = data.filter((i,index)=> {
return (reg.test(i.name)
);
});
console.log(filtered);
this.setState({resoults:filtered})
}
render() {
return (
<div>
<input onChange={this.handleChange.bind(this)} type="text" ref="input"/>
<h3>You typed: {this.state.inputValue}</h3>
<AutoComplete resoults={this.state.resoults} />
</div>
);
}
}
export default App;
import React, {Component} from 'react';
class AutoComplete extends Component {
render() {
return (
<div>
<h4>autocompleteComponent</h4>
{this.props.resoults.map((i)=> {
return (
<ul>
<li>{i.name}</li>
</ul>
);
})}
</div>
);
}
}
export default AutoComplete;
I found myself in this position many times, but I got to the conclusion that it's better to compute the autocomplete options (in your case) without having them in the state of your component.
As I have used them until now, the state and props of a component should represent minimal data needed to render that specific component. Since you have your input value in the state, having the autocomplete options there also seems redundant to me. So here is what I propose:
class App extends Component {
this.state = {
inputValue: '',
};
handleChange(e) {
const inputValue = e.target.value;
this.setState({
inputValue,
});
}
computeResults() {
const {inputValue} = this.state;
// your functionality for computing results here
}
render() {
const {inputValue} = this.state;
const results = this.computeResults();
return (
<div>
<input type="text" onChange={this.handleChange.bind(this)} value={inputValue} />
<h2>You typed: {inputValue}</h2>
<Autocomplete results={results} />
</div>
);
}
}
Notes
Since your results come synchronously, via the .json import, this seems the perfect solution to me. If you want to get them via fetch or anything else, then you'll have to figure out a slightly different approach, but keep in mind that the state of your component should not contain redundant data.
Stop using ref with string value! and use refs when there is absolutely no other way because a React component should not generally deal with DOM operations directly. If you really need to use refs, use ref callbacks.
Hope this helps!
Use another function and setState callBack:
handleChange() {
let inputValue = this.refs.input.value;
this.setState(
{
inputValue: inputValue
},
() => this.secondFunc()
);
}
secondFunc() {
let regular = '^' + this.state.inputValue;
let reg = new RegExp(regular, 'i');
let filtered = data.filter((i, index) => {
return reg.test(i.name);
});
console.log(filtered);
this.setState({ resoults: filtered });
}
Edit: I don't want to call handleChange only if the button has been clicked. It has nothing to do with handleClick. I gave an example in the #shubhakhatri answer's comment.
I want to change the input value according to state, the value is changing but it doesn't trigger handleChange() method. How can I trigger handleChange() method ?
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: 'random text'
}
}
handleChange (e) {
console.log('handle change called')
}
handleClick () {
this.setState({value: 'another random text'})
}
render () {
return (
<div>
<input value={this.state.value} onChange={this.handleChange}/>
<button onClick={this.handleClick.bind(this)}>Change Input</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
Here is the codepen link: http://codepen.io/madhurgarg71/pen/qrbLjp
You need to trigger the onChange event manually. On text inputs onChange listens for input events.
So in you handleClick function you need to trigger event like
handleClick () {
this.setState({value: 'another random text'})
var event = new Event('input', { bubbles: true });
this.myinput.dispatchEvent(event);
}
Complete code
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: 'random text'
}
}
handleChange (e) {
console.log('handle change called')
}
handleClick () {
this.setState({value: 'another random text'})
var event = new Event('input', { bubbles: true });
this.myinput.dispatchEvent(event);
}
render () {
return (
<div>
<input readOnly value={this.state.value} onChange={(e) => {this.handleChange(e)}} ref={(input)=> this.myinput = input}/>
<button onClick={this.handleClick.bind(this)}>Change Input</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
Codepen
Edit:
As Suggested by #Samuel in the comments, a simpler way would be to call handleChange from handleClick if you don't need to the event object in handleChange like
handleClick () {
this.setState({value: 'another random text'})
this.handleChange();
}
I hope this is what you need and it helps you.
I tried the other solutions and nothing worked. This is because of input logic in React.js has been changed. For detail, you can see this link: https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04.
In short, when we change the value of input by changing state and then dispatch a change event then React will register both the setState and the event and consider it a duplicate event and swallow it.
The solution is to call native value setter on input (See setNativeValue function in following code)
Example Code
import React, { Component } from 'react'
export class CustomInput extends Component {
inputElement = null;
// THIS FUNCTION CALLS NATIVE VALUE SETTER
setNativeValue(element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value);
} else {
valueSetter.call(element, value);
}
}
constructor(props) {
super(props);
this.state = {
inputValue: this.props.value,
};
}
addToInput = (valueToAdd) => {
this.setNativeValue(this.inputElement, +this.state.inputValue + +valueToAdd);
this.inputElement.dispatchEvent(new Event('input', { bubbles: true }));
};
handleChange = e => {
console.log(e);
this.setState({ inputValue: e.target.value });
this.props.onChange(e);
};
render() {
return (
<div>
<button type="button" onClick={() => this.addToInput(-1)}>-</button>
<input
readOnly
ref={input => { this.inputElement = input }}
name={this.props.name}
value={this.state.inputValue}
onChange={this.handleChange}></input>
<button type="button" onClick={() => this.addToInput(+1)}>+</button>
</div>
)
}
}
export default CustomInput
Result
I think you should change that like so:
<input value={this.state.value} onChange={(e) => {this.handleChange(e)}}/>
That is in principle the same as onClick={this.handleClick.bind(this)} as you did on the button.
So if you want to call handleChange() when the button is clicked, than:
<button onClick={this.handleChange.bind(this)}>Change Input</button>
or
handleClick () {
this.setState({value: 'another random text'});
this.handleChange();
}
In a functional component you can do this, let's assume we have a input[type=number]
const MyInputComponent = () => {
const [numberValue, setNumberValue] = useState(0);
const numberInput = useRef(null);
/**
* Dispatch Event on Real DOM on Change
*/
useEffect(() => {
numberInput.current.dispatchEvent(
new Event("change", {
detail: {
newValue: numberValue,
},
bubbles: true,
cancelable: true,
})
);
}, [numberValue]);
return (
<>
<input
type="number"
value={numberValue}
ref={numberInput}
inputMode="numeric"
onChange={(e) => setNumberValue(e.target.value)}
/>
</>
)
}
The other answers talked about direct binding in render hence I want to add few points regarding that.
You are not recommended to bind the function directly in render or anywhere else in the component except in constructor. Because for every function binding a new function/object will be created in webpack bundle js file hence the bundle size will grow. Your component will re-render for many reasons like when you do setState, new props received, when you do this.forceUpdate() etc. So if you directly bind your function in render it will always create a new function. Instead do function binding always in constructor and call the reference wherever required. In this way it creates new function only once because constructor gets called only once per component.
How you should do is something like below
constructor(props){
super(props);
this.state = {
value: 'random text'
}
this.handleChange = this.handleChange.bind(this);
}
handleChange (e) {
console.log('handle change called');
this.setState({value: e.target.value});
}
<input value={this.state.value} onChange={this.handleChange}/>
You can also use arrow functions but arrow functions also does create new function every time the component re-renders in certain cases. You should know about when to use arrow function and when are not suppose to. For detailed explation about when to use arrow functions check the accepted answer here
you must do 4 following step :
create event
var event = new Event("change",{
detail: {
oldValue:yourValueVariable,
newValue:!yourValueVariable
},
bubbles: true,
cancelable: true
});
event.simulated = true;
let tracker = this.yourComponentDomRef._valueTracker;
if (tracker) {
tracker.setValue(!yourValueVariable);
}
bind value to component dom
this.yourComponentDomRef.value = !yourValueVariable;
bind element onchange to react onChange function
this.yourComponentDomRef.onchange = (e)=>this.props.onChange(e);
dispatch event
this.yourComponentDomRef.dispatchEvent(event);
in above code yourComponentDomRef refer to master dom of your React component for example <div className="component-root-dom" ref={(dom)=>{this.yourComponentDomRef= dom}}>
Approach with React Native and Hooks:
You can wrap the TextInput into a new one that watches if the value changed and trigger the onChange function if it does.
import React, { useState, useEffect } from 'react';
import { View, TextInput as RNTextInput, Button } from 'react-native';
// New TextInput that triggers onChange when value changes.
// You can add more TextInput methods as props to it.
const TextInput = ({ onChange, value, placeholder }) => {
// When value changes, you can do whatever you want or just to trigger the onChange function
useEffect(() => {
onChange(value);
}, [value]);
return (
<RNTextInput
onChange={onChange}
value={value}
placeholder={placeholder}
/>
);
};
const Main = () => {
const [myValue, setMyValue] = useState('');
const handleChange = (value) => {
setMyValue(value);
console.log("Handling value");
};
const randomLetters = [...Array(15)].map(() => Math.random().toString(36)[2]).join('');
return (
<View>
<TextInput
placeholder="Write something here"
onChange={handleChange}
value={myValue}
/>
<Button
title='Change value with state'
onPress={() => setMyValue(randomLetters)}
/>
</View>
);
};
export default Main;
I know what you mean, you want to trigger handleChange by click button.
But modify state value will not trigger onChange event, because onChange event is a form element event.
I had a similar need and end up using componentDidMount(), that one is called long after component class constructor (where you can initialize state from props - as an exmple using redux )
Inside componentDidMount you can then invoke your handleChange method for some UI animation or perform any kind of component properties updates required.
As an example I had an issue updating an input checkbox type programatically, that's why I end up using this code, as onChange handler was not firing at component load:
componentDidMount() {
// Update checked
const checkbox = document.querySelector('[type="checkbox"]');
if (checkbox)
checkbox.checked = this.state.isChecked;
}
State was first updated in component class constructor and then utilized to update some input component behavior
Try this code if state object has sub objects like this.state.class.fee. We can pass values using following code:
this.setState({ class: Object.assign({}, this.state.class, { [element]: value }) }