I have a form with a single textarea. When text is entered into this textarea a new textarea should be displayed under the current one. If this new textarea has text entered then again another new one shows underneath (and on and on...).
In order to prevent a new textarea being added every time text is entered (for example if there are 3 textareas and the user focuses and changes the text in the first) I am storing the activeBulletPointId in my state, and when text is entered in it I am checking to see if it is the last bullet point in the array.
addNewBulletToEnd = () => {
let lastBulletId = this.state.data.slice(-1);
lastBulletId = lastBulletId[0].id;
if (this.state.activeBulletPointId === lastBulletId) {
const newBulletPoint = { id: this.generateId(), title: 'Click to add' };
this.setState({ data: this.state.data.concat(newBulletPoint) });
}
}
The issue I have is that when rendering my list I am unsure how to pass the id to the onFocus function.
handleFocus = (e) => {
console.log(e); //Can I get the ID here?
if (this.state.activeBulletPointId !== selectedBulletPointId) {
this.setState({ activeBulletPointId: selectedBulletPointId });
}
}
render() {
const bulletList = this.state.data.map((bulletPoint) => {
const reduxFormName = `${this.props.placeholder}-${bulletPoint.id}`;
return (
<div key={bulletPoint.id} className="bullet-point-input">
<SelectInputType
placeholder={reduxFormName}
type="textarea"
onChange={this.updateText}
onFocus={this.handleFocus}
handleKeyPress={this.handleKeyPress(reduxFormName)}
handleKeyDown={this.handleKeyDown}
noLabel
/>
</div>
);
});
return (
<div className="bullet-point-list">
{bulletList}
</div>
);
}
The <SelectInputType> component is what renders my redux-form <Field> component.
You could create a handler for each field. So you would avoid keeping data in DOM (as attributes) and keep it in handler's scope.
Unless you have hundreds of fields this wont hit overall performance.
setActiveBullet = activeBulletPointId => {
if (this.state.activeBulletPointId !== activeBulletPointId ) {
this.setState({ activeBulletPointId });
}
}
render() {
const bulletList = this.state.data.map((bulletPoint) => {
const reduxFormName = `${this.props.placeholder}-${bulletPoint.id}`;
return (
<div key={bulletPoint.id} className="bullet-point-input">
<SelectInputType
placeholder={reduxFormName}
type="textarea"
onChange={this.updateText}
onFocus={() => this.setActiveBullet(bulletPoint.id)}
handleKeyPress={this.handleKeyPress(reduxFormName)}
handleKeyDown={this.handleKeyDown}
noLabel
/>
</div>
);
});
return (
<div className="bullet-point-list">
{bulletList}
</div>
);
}
Related
I'm new in ReactJS. I have a task - to do an app like Notes. User can add sublist to his notes, and note have to save to the state in subarray. I need to save sublist in the array inside object. I need to get state like this:
[...notes, { _id: noteId, text: noteText, notes: [{_id: subNoteId, text: subNoteText, notes[]}] }].
How can I to do this?
Sandbox here: https://codesandbox.io/s/relaxed-lamarr-u5hug?file=/src/App.js
Thank you for any help, and sorry for my English
const NoteForm = ({ saveNote, placeholder }) => {
const [value, setValue] = useState("");
const submitHandler = (event) => {
event.preventDefault();
saveNote(value);
setValue("");
};
return (
<form onSubmit={submitHandler}>
<input
type="text"
onChange={(event) => setValue(event.target.value)}
value={value}
placeholder={placeholder}
/>
<button type="submit">Add</button>
</form>
);
};
const NoteList = ({ notes, saveNote }) => {
const renderSubNotes = (noteArr) => {
const list = noteArr.map((note) => {
let subNote;
if (note.notes && note.notes.length > 0) {
subNote = renderSubNotes(note.notes);
}
return (
<div key={note._id}>
<li>{note.text}</li>
<NoteForm placeholder="Enter your sub note" saveNote={saveNote} />
{subNote}
</div>
);
});
return <ul>{list}</ul>;
};
return renderSubNotes(notes);
};
export default function App() {
const [notes, setNotes] = useState([]);
const saveHandler = (text) => {
const trimmedText = text.trim();
const noteId =
Math.floor(Math.random() * 1000) + trimmedText.replace(/\s/g, "");
if (trimmedText.length > 0) {
setNotes([...notes, { _id: noteId, text: trimmedText, notes: [] }]);
}
};
return (
<div>
<h1>Notes</h1>
<NoteList notes={notes} saveNote={saveHandler} />
<NoteForm
saveNote={saveHandler}
placeholder="Enter your note"
/>
</div>
);
}
The code in your saveHandler function is where you're saving your array of notes.
Specifically, this line:
setNotes([...notes, { _id: noteId, text: trimmedText, notes: [] }]);
But at the moment you're saving an empty array. What if you create another stateful variable called currentNote or something like that, relative to whatever note the user is currently working on within the application? While they are working on that note, the stateful currentNote object is updated with the relevant data, e.g. noteID, content, and parentID. Then, when the user has finished editing that particular note, by either pressing save or the plus button to add a new subnote, etc, that should fire a function such as your saveHandler to add the currentNote object to the "notes" array in the stateful "notes" variable. I'm not sure I like that the stateful variable notes contains an array within it called notes as well. I think this may cause confusion.
But in short, your setNotes line could change to something like (bear with me my JS syntax skills suck):
let newNotes= [...notes.notes];
newNotes.push(currentNote);
setNotes([...notes, { _id: noteId, text: trimmedText, notes: newNotes }]);
Wherein your stateful currentNote object is copied into the notes array on every save.
This is the render method, how i am calling the handler and setting the reactstrap checkbox.
this.state.dishes.map(
(dish, i) => (
<div key={i}>
<CustomInput
type="checkbox"
id={i}
label={<strong>Dish Ready</strong>}
value={dish.ready}
checked={dish.ready}
onClick={e => this.onDishReady(i, e)}
/>
</div>))
The handler for the onClick listener, I've tried with onchange as well but it apears that onchange doesnt do anything, idk why?
onDishReady = (id, e) => {
console.log(e.target.value)
var tempArray = this.state.dishes.map((dish, i) => {
if (i === id) {
var temp = dish;
temp.ready = !e.target.value
return temp
}
else {
return dish
}
})
console.log(tempArray)
this.setState({
dishes: tempArray
});
}
The event.target.value isn't the "toggled" value of an input checkbox, but rather event.target.checked is.
onDishReady = index => e => {
const { checked } = e.target;
this.setState(prevState => {
const newDishes = [...prevState.dishes]; // spread in previous state
newDishes[index].ready = checked; // update index
return { dishes: newDishes };
});
};
The rendered CustomInput reduces to
<CustomInput
checked={dish.ready}
id={i}
label={<strong>DishReady</strong>}
onChange={this.onDishReady(i)}
type="checkbox"
/>
No need to pass in a value prop since it never changes.
Note: Although an onClick handler does appear to work, semantically it isn't quite the correct event, you want the logic to respond to the checked value of the checkbox changing versus a user clicking on its element.
You can do it this way:
this.setState(function (state) {
const dishes = [...state.dishes];
dishes[id].ready = !e.target.value;
return dishes;
});
I am trying to send the contents of the text box to be displayed in the spam when I click on the button
class Hello extends Component {
state = {
texto: ""
}
changeText = () =>{
this.setState({texto: this.state.texto})
}
render() {
return (
<div>
<input type = "text" defaultValue = {this.state.texto}></input>
<p>{this.state.texto}</p>
<button onClick = {this.changeText}>Click</button>
</div>
);
}
}
export default Hello;
function App() {
return (
<div>
<Hello />
</div>
);
}
The idea is that when you click on the button it shows the ones in the text box inside the span.
I am new to react and this clarifies some doubts about the concepts.
thanks guys.
Carlos!
First of all, you must update your input so your code can work properly. In your input, do this instead:
<input type="text" value={this.state.texto} onChange={() => this.onChangeHandler(e.target.value)}></input>
Then, inside your class, you create an onChangeHandler to deal with the data from input first:
onChangeHandler = (e) => {
this.setState({
texto: e
});
}
Ok, we're almost there, now you must create another state item, so you can use it for your final input:
state = {
texto: "",
textoFinished: ""
}
Then, correct your onChange envent:
changeText = () =>{
this.setState({textoFinished: this.state.texto})
}
Now, to access the new value, just go to this.state.textoFinished. Happy coding!
You need to update the state using target value after clicking on it.
Something Like this :
state = {
texto: ""
}
onChangeHandler = (e) => {
const value = e.target.value;
this.setState({texto: value});
}
OR
onChangeHandler = (e) => {
const texto = e.target.value;
this.setState({texto});
}
Fairly new to coding, and especially to React. Have seen other similar questions but can't seem to apply the answers to my code.
I'm trying to update the 'validate' state when the input length reaches 5 and I get the 'maximum update depth exceeded' error. From what I understand when the length reaches 5 it re-renders the DOM, finds that the length = 5 and so begins to call itself recursively (correct me if I'm wrong!), and so I'm trying to work out how to execute validationHandler() when the number reaches 5 only once.
class App extends Component {
state = {
inputLength: '0',
validate: 'Too short'
}
changeHandler = (event) => {
this.setState({ inputLength: event.target.value.length });
};
validationHandler = () => {
if (this.state.inputLength > 4) {
this.setState({ validate: "Enough"})
}
};
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler.bind(this)}
/>
<Validation
change={this.validationHandler()}
validate={this.state.validate}/>
</div>
);
}
}
I have a separate validation component.
const validation = (props) => {
return (
<div className="validation">
<p onChange={props.change}>{props.validate}</p>
</div>
)
};
Thanks in advance!
You have exactly the same issue as ReactJS: Maximum update depth exceeded error.
However, while applying the solution, i.e.
<Validation
change={this.validationHandler}
validate={this.state.validate}/>
will fix the error, it won't make your app work. The validationHandler method will never be called because p elements do not trigger a change event.
<p onChange={props.change}>{props.validate}</p>
Instead you want to validate the input length whenever the input changes, so that should happen inside the changeHandler method:
changeHandler = (event) => {
this.setState({
inputLength: event.target.value.length, // remove if not needed
validate: event.target.value.length > 4 ? "Enough" : "Too short",
});
};
and
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler}
/>
<Validation validate={this.state.validate}/>
</div>
);
}
Calling .bind(this) has no effect because this.changeHandler is an arrow function.
Change the code to below
class App extends Component {
state = {
validate: 'Too short'
}
changeHandler = (event) => {
if (event.target.value.length > 4) {
this.setState({ validate: "Enough"})
} else {
this.setState({ validate: "Too short"})
}
};
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler.bind(this)}
/>
<Validation
validate={this.state.validate}/>
</div>
);
}
}
and component to
const validation = (props) => {
return (
<div className="validation">
<p>{props.validate}</p>
</div>
)
};
I need to add an input box (input-0, input-1...) each time a button is clicked.Following is the relevant code.
// State
this.state = {
newText: {}
};
// Code to add dynamic input text
addInputText = () => {
let dynamicTextsNo = Object.keys(this.state.newText).length;
let newInputId = dynamicTextsNo+1;
let dynamicTextArr = this.state.newText;
let newTextId = 'input-'+dynamicTextsNo;
dynamicTextArr[newTextId] = '';
let newState = { ...this.state, newText: dynamicTextArr }
this.setState( newState );
}
// Code to render dynamic input text.
dynamicTextArea = () => {
return Object.keys(this.state.newText).map( ( key ) => {
return ( <InputGroup key={key} borderType='underline'>
<Input placeholder='Type your text here' value={this.state.newText[key]} onChange={this.changeInput}/>
</InputGroup>
);
});
}
// Render function
render() {
return <View>{this.dynamicTextArea()}</View>
}
// handle input
changeInput = (e) => {
console.log( e.target.value ); // **this comes out to be undefined.**
}
Why is e.target.value in changeInput function undefined?
P.S. Jsfiddle link of the full code: https://jsfiddle.net/johnnash03/9by9qyct/1/
Unlike with the browser text input element, the event argument passed to React Native TextInput.onChange callback does not have a property called target.
Instead, use
<TextInput
onChange={(event) => event.nativeEvent.text}
/>
or
<TextInput
onChangeText={(text) => text}
/>
You must use bind like so <Input placeholder='Type your text here' value={this.state.newText[key]} onChange={this.changeInput.bind(this)}/>