Inputs I'm creating are unDisabling all at once as the state changes.
How can I enable/disable direct input? Mb something wrong with refs... I dunno
class TaskRow extends Component {
constructor(props) {
super(props);
this.myRef = createRef();
this.state = {
edit: true,
};
}
editTask = () => {
this.setState({ edit: false })
};
render() {
const { edit } = this.state;
return (
<div>
<ul className="taskList">
<div className="tasksContainer">
{tasks.map(task => (
<div className="taskDiv" key={task.id}>
<input type="checkbox" checked={task.completed}/>
<input
ref={this.myRef}
type="text"
content={task.text}
disabled={edit}
/>
<button onClick={this.editTask}>Редактировать</button>
</div>
))}
</div>
</ul>
</div>
)
}
}
What you can do is in constructor,
const inputState = {};
tasks.forEach((task) => inputState[`input_${task.id}] = true)
this.state = {
...inputStates
}
Then in JSX
disabled={this.state[`input_${task.id}`]}
<button onClick={() => this.editTask(`input_${task.id})}>Редактировать</button>
Then in Function
editTask = (inputState) => {
this.setState({ [inputState]: false })
};
I'm just giving you idea, Maintain separate states for each, set them apart. Look for spelling mistakes if any
This is because you are using single state i.e. edit for all the input fields. So, when the state is updated, its updated for all the fields. You have to use different states for different input. One of the suggestion might be that, You can use array of boolean in the edit state update it according to the input fields actions
Since you are binding all the inputs to the single this.state.editTask property, whenever you'll modify it, all inputs will change because all the inputs have their disabled attributes bound to {this.editTask}.
What you can do is that you can define an edit property in each task same as you're tackling the complete property for checkboxes.
Please see the StackBlitz example I've created to see it working.
I.e. tasks would be:
[{
id: 1,
completed: true,
edit: true
}, {
id: 2,
completed: false,
edit: false
}, {
id: 3,
completed: true,
edit: true
}, {
id: 4,
completed: false,
edit: true
}, {
id: 5,
completed: true,
edit: false
}
]
And then you can bind the html in the following way:
<ul className="taskList">
<div className="tasksContainer">
{this.state.tasks.map(task => (
<div className="taskDiv" key={task.id}>
<input type="checkbox" checked={task.completed}/>
<input className='taskInput'
type="text"
content={task.text}
disabled={task.edit !== true}
/>
<button onClick={() => { this.editTask(task) }}>Редактировать</button>
</div>
))}
</div>
</ul>
Related
I am trying to write a delete method in order to delete an element from a list, first of all I am not being able to write it in a setState function so I have it as a direct function call, How can I manage to signal a re-render after the direct function or manage to place the function in the setState method for automatic re-render?
class TASKMANAGER extends Component {
constructor(props){
super(props);
this.state= {
name: "",
description:"",
priority: "urgent",
tasklist: [],
}
this.handleTitleChange= this.handleTitleChange.bind(this);
//this.handleDescriptionChange= this.handleDescriptionChange.bind(this);
//this.handlePriorityChange= this.handleDescriptionChange.bind(this);
this.handleClick= this.handleClick.bind(this);
}
handleTitleChange = event => {
this.setState( {
name: event.target.value
})
};
handleDescriptionChange = event => {
this.setState({
description: event.target.value
})
};
handlePriorityChange = event => {
this.setState({
priority: event.target.value
})
};
handleClick = event => {
this.setState((state) => {
const tasklist = [
...state.tasklist,
[
state.name,
state.description,
state.priority
]
];
return {
tasklist
};
});
//console.log(this.state.tasklist);
};
handleDelete = index => {
this.setState(() => {
this.state.tasklist.splice(index, 1)
});
console.log(this.state.tasklist)
} THIS ONE IS THE FUNCTION I CANNOT SET TO WORK TO TRIGGER THE AUTO RE-RENDER
render() {
const task_item = this.state.tasklist.map((arr, index) => (
<li
key= {index}
className= 'task'>
Task: {arr[0]} <br />
Description: {arr[1]} <br />
Priority: {arr[2]} <br />
<div className='delete-button' onClick={
/*() => {this.state.tasklist.splice(index, 1);}*/ THIS ONE IS THE DIRECT FUNCTION THAT WORKS, BUT DOESN'T TRIGGER THE RE-RENDER, IT SHOWS WHEN I TYPE AGAIN ON THE INPUTS
this.handleDelete
}>delete</div>
</li>
))
return (
<div>
<div className= 'task-form'>
<form>
<div>
<label>Name your task!</label>
<input type= 'text' id='task-title' value={this.state.name} onChange={this.handleTitleChange} />
</div>
<div>
<label>Description?</label>
<textarea id='description' value={this.state.description} onChange={this.handleDescriptionChange}/>
</div>
<div>
<label>Priority?</label>
<select value={this.state.priority} onChange={this.handlePriorityChange}>
<option value='urgent'>Urgent</option>
<option value='regular'>Regular</option>
<option value='Can wait'>Can wait</option>
</select>
</div>
</form>
<button onClick={this.handleClick}>PRESS</button>
</div>
<div className='list-items'>
<ul className='list-render'>
{task_item}
</ul>
</div>
</div>
)
}}
export default TASKMANAGER
You shouldn't be making any mutations to the current state, but instead build a new state from the existing state, generating a new, filtered array along the way
handleDelete = index => {
this.setState((state) => ({
...state,
tasklist: state.taskList.filter((_,i) => i != index)
}));
}
When you map your taskList to JSX below, you will need to avoid using the index of the item as key, because the optimizations react makes using the key value will be operating under broken assumptions. Use a key value that remains constant and unique per item. Perhaps its name, or an identifier that is attached to it when created.
There is no need to assign the list. Just splice it. Use something like this to change the state:
delete={()=>{this.setState({phase:1-this.state.phase});
this.state.list.splice(index,1)}}
Hello everyone, I am trying to passing a method through a context api component to another component which, i have a map function there. I want my showInfo state changes to true or false depending on the button clicking, when i clicked the button, all the showInfo's of my states is changes, so thats not what i want, I want that specific item to change when i press to it. Can someone explaine where is the mistake that i've made?
MY CONTEXT APİ
import React from "react";
export const ToursContext = React.createContext();
class ToursContextProvider extends React.Component {
constructor(props) {
super(props);
this.changeState = this.changeState.bind(this);
this.state = {
tours: [
{
id: 0,
imageURL:
"https://images.unsplash.com/photo-1524231757912-21f4fe3a7200?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1351&q=80",
title: "İstanbul'un Güzelliğinin Sadece Bir Parçası Galata Kulesi",
showInfo: true,
info: "LOREM İPSUM AMET 1",
},
{
id: 1,
imageURL:
"https://images.unsplash.com/photo-1541432901042-2d8bd64b4a9b?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=1319&q=80",
title: "Tarihi Süleymaniye Camii",
showInfo: true,
info: "LOREM İPSUM AMET 2",
},
],
};
}
changeState(itemdelete) {
this.setState({
showInfo: !this.state.showInfo,
});
console.log(itemdelete);
}
render() {
return (
<ToursContext.Provider
value={{ ...this.state, changeState: this.changeState }}
>
{this.props.children}
</ToursContext.Provider>
);
}
}
export default ToursContextProvider;
MY MAP LIST COMPONENT
import React from "react";
import { ToursContext } from "../contexts/Tours";
function Tours() {
return (
<div className="container">
<div className="row">
<ToursContext.Consumer>
{(value) => {
const { changeState } = value;
return value.tours.map((item) => (
<div className="col-md-4" key={item.id}>
<div className="card bg-dark text-white">
<img src={item.imageURL} className="card-img" alt="..." />
<div className="card-img-overlay">
<h5 className="card-title">{item.title}</h5>
<button
type="button"
onClick={changeState.bind(this, item)}
className="btn-sm btn-primary"
>
Bilgiyi Göster!
</button>
</div>
{value.showInfo ? "true" : "false"}
</div>
</div>
));
}}
</ToursContext.Consumer>
</div>
</div>
);
}
export default Tours;
You state is atomic. This means that it is treated as a single value. With classes, you have option to modify state object partially. For example, you have object with fields a and b. You can change both fields at once, only a or only b. But there is no option to modify state deeply. Let's imagine that you have state object like this:
{
"a": { "subfield_1": [], "subfield_2": "some string"},
"b": 3
}
You again, can modify a or b, but if you want to add item into array a.subfield_1 or change a.subfield_2, you will have to modify whole a, like this:
setState({
a: {
...a,
subfield_1: this.state.a.subfield_1.concat("new item"),
},
});
In you case, to change something inside tours key, you will have to modify whole tours key. It would be something like this:
changeState(itemdelete) {
this.setState({
tours: tours.map((item) =>
item.id !== itemdelete.id ? item : { ...item, showInfo: !item.showInfo }
),
});
}
I am pretty new to the wonderful world of React.
I have two inputs passing data through from an API that renders a list of options. And I want to send the selected inputs from those options back to the parent in the input fields to display for another search.
I have tried passing state down to them and render them them optionally with both a ternary and an if else statement in the "SearchCityList" component in several ways but I either get both lists rendered and they would have to choose between one list that is doubled to put in each input field or it only puts the selected value in one input. Would appreciate any & all suggestions Thanks!
class Form extends Component {
state = {
showComponent: false,
showComponent2: false,
};
// open/close control over SearchCity component box
openSearch = () => {
this.setState({ showComponent: true });
};
openSearch2 = () => {
this.setState({ showComponent2: true });
};
closeSearch = () => {
this.setState({
showComponent: false,
showComponent2: false
});
};
// Passed down cb function to get selected city search in selectCity component
GoingTo = (flights) => {
this.setState({ GoingTo: [flights] });
};
LeavingFrom = (flights) => {
this.setState({ LeavingFrom: [flights] });
};
render() {
return (
<div>
<form className="form-fields container">
<div className="inputs">
<h1>Search for a flight!</h1>
<div className="depart">
<input
onClick={this.openSearch}
className="flight-search"
placeholder="Leaving From"
value={this.state.LeavingFrom}
></input>
<input type="date"></input>
</div>
<div className="Returning">
<input
onClick={this.openSearch2}
className="flight-search"
placeholder="Going To "
value={this.state.GoingTo}
></input>
<input type="date" placeholder="Returning"></input>
</div>
</div>
<button>Check Flights!</button>
</form>
{this.state.showComponent || this.state.showComponent2 ? (
<SearchCity
openSearch={this.openSearch}
openSearch2={this.openSearch2}
flightSearch={this.state.flightSearch}
closeSearch={this.closeSearch}
GoingTo={this.GoingTo}
LeavingFrom={this.LeavingFrom}
onSearchSubmission={this.onSearchSubmission}
closeSearch={this.closeSearch}
/>
) : null}
</div>
);
}
}
export default Form;
class SearchCity extends Component {
state = {
LeavingFrom: "",
GoingTo: "",
search: "",
flightSearch: [],
};
// Search submission / api call
onSearchSubmission = async (search) => {
const response = await Axios.get(
{
headers: {
"
useQueryString: true,
},
}
);
// set New state with array of searched flight data sent to searchCity component
const flightSearch = this.setState({ flightSearch: response.data.Places });
};
// Callback function to send search/input to parent "Form" component
submitSearch = (e) => {
e.preventDefault();
this.onSearchSubmission(this.state.search);
};
// closeSearch callback function sent from Form component to close pop up search box when X is pressed
closeSearch = () => {
this.props.closeSearch();
};
render() {
return (
<div className="container search-list">
<form onChange={this.submitSearch}>
<i className="fas fa-times close-btn" onClick={this.closeSearch}></i>
<input
onChange={(e) => this.setState({ search: e.target.value })} //query-search api
value={this.state.search}
className="search-input"
type="text"
placeholder="Search Locations"
></input>
<div className="search-scroll">
<SearchCityList
openSearch={this.props.openSearch}
openSearch2={this.props.openSearch2}
LeavingFrom={this.props.LeavingFrom}
GoingTo={this.props.GoingTo}
flightSearch={this.state.flightSearch}
/>
</div>
</form>
</div>
);
}
}
export default SearchCity;
function SearchCityList({ flightSearch, LeavingFrom, GoingTo }) {
const renderList = flightSearch.map((flights) => {
return (
<div>
<SelectCityLeaving LeavingFrom={LeavingFrom} flights={flights} />
<SelectCityGoing GoingTo={GoingTo} flights={flights} />
</div>
);
});
return <div>{renderList}</div>;
}
export default SearchCityList;
First of all, when dealing with state, make sure you initialize in the constructor and also ensure you bind your handlers to this component instance as this will refer to something else in the handlers if you don't and you won't be able to call this.setState().
constructor(props) {
super(props); // important
state = {
// your state
};
// make sure to bind the handlers so `this` refers to the
// component like so
this.openSearch = this.openSearch.bind(this);
}
I have been reading some threads on SO but I could not figure out how to solve this issue or why it's happening. Can someone explain it like I'm 5?
Warning: A component is changing a controlled input of type text to be
uncontrolled. Input elements should not switch from controlled to
uncontrolled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component
I am developing a lesson creator, and the user must be able to open an existing lesson, hence the input fields must be programmatically filled with the content of the existing lesson.
My constructor:
constructor(props) {
super(props);
this.state = {
lessonID: -1,
sectionsArray: [],
title: 'No title',
type: 'r',
language: 'gb',
book: 'booka',
level: '1',
loading: false,
saved: true,
messageBox: '',
lessonOpenModal: false,
}
this._state = this.state;
this.updateSectionsFromChild = this.updateSectionsFromChild.bind(this);
this.sectionAdd = this.sectionAdd.bind(this);
this.sectionRemove = this.sectionRemove.bind(this);
this.menuInput = this.menuInput.bind(this);
this.menuDropDown = this.menuDropDown.bind(this);
this.lessonCreate = this.lessonCreate.bind(this);
this.lessonSave = this.lessonSave.bind(this);
this.lessonDelete = this.lessonDelete.bind(this);
this.lessonOpen = this.lessonOpen.bind(this);
this.sections = [];
}
This are the functions that update the controlled components:
menuDropDown(event, data) {
this.setState({
[data.name]: data.value,
saved: false,
});
console.log(data.name);
console.log(data.value);
}
menuInput(event) {
this.setState({
[event.target.name]: event.target.value,
saved: false,
});
}
And then this is the part of code that retrieves the lesson and tries to update the state:
async openLesson(lessonID) {
await ARLessonOpen(lessonID).then((result) => {
this.setState(this._state);
this.setState({
id: result.lesson.id,
language: result.lesson.language,
book: result.lesson.book, // this is a drop down, and it's not causing errors
type: result.lesson.type, // this is a drop down, and it's not causing errors
level: result.lesson.level, // this is a drop down, and it's not causing errors
title: result.lesson.title, // this is an input, and IT'S THE ISSUE
sectionsArray: result.sections.map((section, i) => ({
key: i,
id: i,
title: section.title,
duration: section.duration,
content: section.content,
}))
})
}).catch(function(error) {
console.log(error);
});
}
The only field that is not working is the 'title' and I can't understand why. How can I update the input value programmatically?
JSX:
renderSections = () => {
if (this.state.sectionsArray.length > 0) {
return this.state.sectionsArray.map((section, i) =>
<LessonSection
key={section.id}
id={section.id}
title={section.title}
duration={section.duration}
content={section.content}
sectionRemove={this.sectionRemove}
sectionAdd={this.sectionAdd}
updateSectionsFromChild={this.updateSectionsFromChild}
/>
)
} else {
return (
<div style={{color: 'black'}}>
<Button
size='mini'
icon='plus square outline'
onClick={this.sectionAdd} />
Add a section to start creating your lesson.
</div>
)
}
}
render() {
return (
<div className='Lesson-editor'>
{this.state.messageBox}
<div style={{display: 'none'}}>
<DefaultLoader
active={this.state.loading}
message={this.state.message}
/>
</div>
<div className="Lesson-editor-menu Small-font">
<div className="Menu-buttons">
<Button
size='mini'
icon='plus square outline'
onClick={this.sectionAdd} />
<Button
size='mini'
icon='file outline'
onClick={this.lessonCreate} />
<DialoglessonOpen
open={this.state.lessonOpenModal}
actionOnLessonSelected={(lessonID) => this.openLesson(lessonID)}
onCancel={() => this.setState({lessonOpenModal: false})} />
<Button size='mini' icon='open folder outline' text='Open lesson' description='ctrl + o' onClick={this.lessonOpen} />
<Button
size='mini'
icon='save outline'
onClick={this.lessonSave} />
<Button
size='mini'
icon='delete'
onClick={this.lessonDelete} />
<Button
size='mini'
icon='delete'
color='red'
onClick={ARClearTables} />
</div>
<Input
className='title'
fluid
placeholder='Lesson title'
value={this.state.title}
name='title'
onChange={this.menuInput}
/>
<div>
<Dropdown
fluid
compact
placeholder='Language'
search
selection
options={lessonLanguages}
//defaultValue='gb'
value={this.state.language}
name='language'
onChange={this.menuDropDown}
/>
<Dropdown
fluid
compact
placeholder='Book'
search
selection
options={lessonBooks}
//defaultValue='booka'
value={this.state.book}
name='book'
onChange={this.menuDropDown}
/>
<Dropdown
fluid
compact
placeholder='Lesson type'
search
selection
options={lessonTypes}
defaultValue='r'
name='type'
onChange={this.menuDropDown}
/>
<Dropdown
fluid
compact
placeholder='Lesson level'
search
selection
options={lessonLevels}
defaultValue='1'
name='level'
onChange={this.menuDropDown}
/>
</div>
</div>
<div className='Sections'>
{ this.renderSections() }
</div>
</div>
);
}
}
The initial value of input forms fields cannot be undefined or null, if you want to control it later. It should be an empty string. If you provide an undefined or null it's uncontrolled component.
In you code, React doesn't see any value to the input fields, so React believe it is a un-controlled component on the first mount. Later, when you add a value to the component React warning you that you can't give a value (controlled component) after you didn't provided a value (uncontrolled component)
I figured it out: the problem is that there was an error in my code. I was assigning a null value to the input field value in state.
async openLesson(lessonID) {
await ARLessonOpen(lessonID).then((result) => {
this.setState(this._state);
this.setState({
/* HERE: I try to access result.lesson but it's null! I should
use result.lesson[0]. So the problem is that I was
assigning a null value to the input field resulting in the error */
id: result.lesson.id,
language: result.lesson.language,
book: result.lesson.book,
type: result.lesson.type,
level: result.lesson.level,
title: result.lesson.title,
sectionsArray: result.sections.map((section, i) => ({
key: i,
id: i,
title: section.title,
duration: section.duration,
content: section.content,
}))
})
}).catch(function(error) {
console.log(error);
});
}
Following is my country selector drop down which pops in as soon as user Focus the input field. I have made a validation onBlur as data cannot be remains empty in the input field. Problem is on selecting the value from the list onBlur fires immediately and then the values gets populated in the dropdown which is causing onBlur validation message to appear on screen.
Let me know how can I manage the situation by removing the onBlur validation once user selects the value from the list.
React Code -
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
blurText: "Field Cannot be left blank",
listData: [],
selectedValue: "",
showList: false,
showBlurText: false
};
}
componentDidMount() {
this.setState({
listData: [
{ id: 1, name: "Australia" },
{ id: 2, name: "Germany" },
{ id: 3, name: "France" }
]
});
}
handleBlur = e => {
if (!e.target.innerHTML) {
this.setState({
showBlurText: true
});
} else {
this.setState({
showBlurText: false
});
}
};
showDataList = () => {
this.setState({
showList: true
});
};
handleSelection = e => {
this.setState({
selectedValue: e.target.innerHTML
});
};
handleChange = e => {
this.setState({ selectedValue: e.target.value });
};
render() {
return (
<div className="App">
<h3>Test Select</h3>
<input
type="text"
name="autosuggest"
value={this.state.selectedValue}
onFocus={this.showDataList}
onBlur={this.handleBlur}
onChange={this.handleChange}
/>
{this.state.showList && (
<ul>
{this.state.listData.map(x => {
return (
<li key={x.id} onClick={this.handleSelection}>
{x.name}
</li>
);
})}
</ul>
)}
<hr />
{this.state.showBlurText && (
<p style={{ color: "red" }}>{this.state.blurText}</p>
)}
</div>
);
}
}
Working Codesandbox with the scenario - https://codesandbox.io/s/charming-brook-h7kin
I have a solution, you can check on this link:
Codesandbox: Click catcher on blur
The issue is you put the onBlur prop to the input element, so when you click an item in the ul element, the onBlur gets triggered.
So what I did is to create a simple click-catcher so when you click outside of input or ul, it will be triggered.
Hope this helps.
Instead of using onBlur you can check validations on onChange event, check the working sandbox here
some tips below that will make your code clean:
Instead of using if-else while validating emptyefields you can directly use !(negate operator) in your handleBlur function.
handleBlur = e => {
this.setState({
showBlurText: !e.target.innerHTML
})
}