Edit an Item in a list - javascript

I am creating a to-do app using react js and semantic-ui-library.
Although Add and delete functions are working perfectly, I am stuck at moving on with edit function. Following are code snippets.
App.js
class App extends Component{
constructor(props){
super(props);
this.state={
item :'',
listItems:[
],
}
this.handleChangeItem = this.handleChangeItem.bind(this);
}
updateItem=(key,item)=>{
const newlistItems = [...this.state.listItems]
newlistItems.map(list=>{
if(list.key===key){
list.item = item;
}
}
)
this.setState({
listItems:newlistItems
})
}
ListView.js
<List.Content>
{
this.props.listItems.map((item, index) => <List.Header key={index} >
<span>
<input
size="50%"
id={index}
value={item}
onChange={(event)=>{this.props.updateItem(event.target.value,index)}}
/>
</List.Content>
I am implementing the edit function in parent component and calling it for onChange method in input field of the List View Component. I am unable to edit the value of the input field in the view component.
Can anybody help me to sort this out?

The problem is with your updateItem function. You can use setState with a callback to access the previous state of your state.
Try the following:
updateItem = (key, item) => {
this.setState(prevState => {
...prevState,
listItems: prevState.listItems.map(list => {
if(list.key === key){
list.item = item;
}
return list;
})
})
}
And maybe on your onChange event you want to pass item instead of index:
onChange={ event => { this.props.updateItem(event.target.value, item) } }
I hope this helps!

Related

ReactJS onClick not working on first element in menu

I'm making a custom dropdown list in reactjs. When I click on any element in the dropdown list I get it's value and put it inside an input and it works just fine. The problem is that the first element returns nothing and I can't get it's value. Also I developed the dropdown list to disappear when I choose any element inside of it. But like I said it works just fine on all elements except the first one.
I solved the problem by setTimeOut and hide the dropdown list in 50 milliseconds. But I don't think this's a right solution.
//Select Component
class Select extends Component {
showList = (e) => {
e.target.closest('.select').classList.add('active');
}
hideList = (e) => {
setTimeout(() => {
e.target.closest('.select').classList.remove('active');
}, 100);
}
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
}
render() {
return (
<input
{...this.props}
type="text"
placeholder={this.props['placeholder']}
onFocus={this.showList}
onBlur={this.hideList}
value={this.state.selectValue}
onChange={this.changeSelectValue}
required
/>
<div className="select">
<div className="select-options menu full-width">
{
this.props.list.map(element => {
return (
<MenuItem text={element} onClick={(e) => this.selectValue(e)} />
)
})
}
</div>
</div>
);
}
}
==================
class MenuItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<p className={"menu-item " + this.props['type'] } {...this.props}>
{this.props['icon'] ? <i className={this.props['icon']} ></i> : null}
<span>{this.props['text']}</span>
</p>
);
}
}
1.use key prop for every MenuItem Component when you are using the list to create a list of MenuItems.
2.Instead of getting value from target in selectValue function directly pass the value from onClick handler.
selectValue = (e , element) => {
this.setState({ selectValue: element })
}
<MenuItem text={element} key={element} onClick={(e) => this.selectValue(e , element)} />
Editted:-
Remove the onBlur handler and put the functionality of hideList inside selectValue function after setState,you can use setState with callback if normal setState doesn't work
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
e.target.closest('.select').classList.remove('active');
}

Filtering Nested Arrays in React JS

I am attempting to filter a list of conversations by participant names. The participant names are properties inside of a participant list and the participant list is contained within a list of conversations.
So far, I have approached the problem by attempting to nest filters:
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.filter(
(participant) => {
return participant.fallback_name.toLowerCase().indexOf(
this.state.searchTerm.toLowerCase()) !== -1;
})
})
This appears to work, insofar as I can confirm (i.e. I put a whole bunch of console.logs throughout an expanded version of the above) that as the searchTerm state is updated, it returns matching the participant and the matching convo. However, filteredConvos is not correctly rendered to reflect the newly filtered array.
I am new to Javascript, React, and Stack Overflow. My best assessment is that I am incorrectly passing my filtered array items back to filteredConvos, but I honestly don't have a enough experience to know.
Any assistance is deeply appreciated.
Further context:
The data source I'm working with is a JSON file provided by
google of an account's Hangouts chat.
HangoutSearch.js:
class HangoutSearch extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
searchTerm: e.target.value
});
}
render() {
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.filter(
(participant) => {
return participant.fallback_name.toLowerCase().indexOf(
this.state.searchTerm.toLowerCase()) !== -1;
})
})
return(
<div>
<Form>
<Form.Control
placeholder='Enter the name of the chat participant'
value={this.state.searchTerm}
onChange={this.handleChange} />
</Form>
<HangoutList filteredConvos={filteredConvos}/>
</div>
);
}
}
export default HangoutSearch;
HangoutList.js
class HangoutList extends Component {
render() {
return(
<ListGroup>
{this.props.filteredConvos.map((convo) => {
return (
<ListGroup.Item key={convo.conversation.conversation.id.id}>
{convo.conversation.conversation.participant_data.map(
(participant) => {
return (
<span key={participant.id.gaia_id}>
{participant.fallback_name}
</span>
)
}
)}
</ListGroup.Item>
)
})}
</ListGroup>
);
}
}
export default HangoutList;
The inner .filter always returns an array, which are truthy in Javascript. You could use .some instead:
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.some((participant) => {
return participant.fallback_name.toLowerCase().indexOf( this.state.searchTerm.toLowerCase()) !== -1;
})
})

ReactJS instant Search with input

Im making my first react project. Im new in JS, HTML, CSS and even web app programming.
What i want to do it is a Search input label. Now its look like this:
Like you can see i have some list of objects and text input.
I Have two components, my ProjectList.js with Search.js component...
class ProjectsList extends Component {
render() {
return (
<div>
<Search projects={this.props.projects} />
<ListGroup>
{this.props.projects.map(project => {
return <Project project={project} key={project.id} />;
})}
</ListGroup>
</div>
);
}
}
export default ProjectsList;
... and ProjectList.js displays Project.js:
How looks Search.js (its not ended component)
class Search extends Component {
state = {
query: ""
};
handleInputChange = () => {
this.setState({
query: this.search.value
});
};
render() {
return (
<form>
<input
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
<p />
</form>
);
}
}
export default Search;
My project have name property. Could you tell me how to code Search.js component poperly, to change displaying projects dynamically based on input in text label? for example, return Project only, if text from input match (i want to search it dynamically, when i start typing m... it shows all projects started on m etc).
How to make that Search input properly? How to make it to be universal, for example to Search in another list of objects? And how to get input from Search back to Parent component?
For now, in react dev tools whatever i type there i get length: 0
Thanks for any advices!
EDIT:
If needed, my Project.js component:
class Project extends Component {
state = {
showDetails: false
};
constructor(props) {
super(props);
this.state = {
showDetails: false
};
}
toggleShowProjects = () => {
this.setState(prevState => ({
showDetails: !prevState.showDetails
}));
};
render() {
return (
<ButtonToolbar>
<ListGroupItem className="spread">
{this.props.project.name}
</ListGroupItem>
<Button onClick={this.toggleShowProjects} bsStyle="primary">
Details
</Button>
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
</ButtonToolbar>
);
}
}
export default Project;
To create a "generic" search box, perhaps you could do something like the following:
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
This revised version of Search takes some additional props which allows it to be reused as required. In addition to the projects prop, you also pass filterProject and onUpdateProjects callbacks which are provided by calling code. The filterProject callback allows you to provide custom filtering logic for each <Search/> component rendered. The onUpdateProjects callback basically returns the "filtered list" of projects, suitable for rendering in the parent component (ie <ProjectList/>).
The only other significant change here is the addition of visibleProjects to the state of <ProjectList/> which tracks the visible (ie filtered) projects from the original list of projects passed to <ProjectList/>:
class Project extends React.Component {
render() {
return (
<div>{ this.props.project }</div>
);
}
}
class ProjectsList extends React.Component {
componentWillMount() {
this.setState({ visibleProjects : [] })
}
render() {
return (
<div>
<Search projects={this.props.projects} filterProject={ (query,project) => (project == query) } onUpdateProjects={ projects => this.setState({ visibleProjects : projects }) } />
<div>
{this.state.visibleProjects.map(project => {
return <Project project={project} key={project.id} />;
})}
</div>
</div>
);
}
}
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
ReactDOM.render(
<ProjectsList projects={[0,1,2,3]} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I will assumes both your Search and ProjectList component have a common parent that contains the list of your projects.
If so, you should pass a function into your Search component props, your Search component will then call this function when the user typed something in the search bar. This will help your parent element decide what your ProjectsLists needs to render :
handleInputChange = () => {
this.props.userSearchInput(this.search.value);
this.setState({
query: this.search.value
});
};
And now, here is what the parent element needs to include :
searchChanged = searchString => {
const filteredProjects = this.state.projects.filter(project => project.name.includes(searchString))
this.setState({ filteredProjects })
}
With this function, you will filter out the projects that includes the string the user typed in their names, you will then only need to put this array in your state and pass it to your ProjectsList component props
You can find the documentation of the String includes function here
You can now add this function to the props of your Search component when creating it :
<Search userSearchInput={searchChanged}/>
And pass the filtered array into your ProjectsList props :
<ProjectsList projects={this.state.filteredProjects}/>
Side note : Try to avoid using refs, the onCHnage function will send an "event" object to your function, containing everything about what the user typed :
handleInputChange = event => {
const { value } = event.target
this.props.userSearchInput(value);
this.setState({
query: value
});
};
You can now remove the ref from your code

Error dynamically updating an array list in ReactJs

I am learning reactJS and so I am trying my hands on an example. This example has a form textfield that can add an item to an existing array on click of a button. I am having errors here as when I enter a text and click on the button, the array list is not updated except I try to make changes to the text entered in the textfield. This is what I am doing:
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
currentName : '',
arrays : ['john', 'james', 'timothy']
}
}
render() {
const showNames = this.state.arrays.map((thisName) => {
const values = <li>{thisName}</li>;
return values;
});
const getText = (e) => {
let value = e.target.value;
this.setState({
currentName : value
})
}
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" onChange={getText}/>
<button type="button" onClick={addToUsers}>Add User</button>
</form>
<ul>
{showNames}
</ul>
</div>
);
}
}
export default App;
There are a host of things wrong with this, but your issue is likely that you need to use setState to modify state.
import React, { Component } from 'react';
class App extends Component {
constructor(){
super();
this.state = {
names: ['john', 'james', 'timothy']
}
}
addToUsers = () => {
this.setState(
prevState => ({
names: [...prevState.names, this.input.value]
})
)
}
render() {
const names = this.state.names.map(
(name, index) => <li key={index}>{name}</li>
)
return (
<div>
<p>Add new name to List</p><br/>
<form>
<input type="text" ref={e => this.input = e} />
<button type="button" onClick={this.addToUsers}>Add User</button>
</form>
<ul>
{names}
</ul>
</div>
)
}
}
export default App;
This quick edit changes a few things:
Uses setState for the addToUsers method
Eliminate onChange tracking and pull the name directly from the input when the button is clicked
Move the addToUsers method out to the component class rather than defining it on render
Rename this.state.arrays to this.state.names
Simplify conversion of this.state.names into list items
Set key on array elements (name list items)
Use prevState in setState to avoid race conditions
You need to make sure you update state using the setState method.
When you update arrays you are reaching into the state object and manipulating the data directly instead of using the method.
Instead try something like:
const addToUsers = () => {
const newArray = this.state.arrays.concat([this.state.currentName]);
this.setState({
arrays: newArray
});
}
You probably must add
onChange={getText}.bind(this)
to your functions.
Also change this
const addToUsers = () => {
this.state.arrays.push(this.state.currentName)
}
to this
const addToUsers = () => {
this.setState({here put your variable})
}

Updating state in react js

I have this code that renders a dropdown component from material ui and it's populated with data coming from a WS.
I set an initial value that is the firs element coming from the WS so when I render the page for the first time I can see the correct value in the dropdown.
My issue is when I try to select a different value on the dropdown, I'm not able to do it and I think is because I'm not updating the state, I have a method called "handleChange" but I'm missing something there but don't know what.
This is the code and hope someone can help with this, I'm new to react and still to practice much more.
import React, { Component } from 'react';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
export default class WebserviceTest extends Component {
constructor() {
super();
this.state = {
data: [],
selected: ''
};
this.renderOptions = this.renderOptions.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
const url = 'https://randomuser.me/api/?results=4';
fetch(url)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
data: findResponse.results,
selected: findResponse.results[0].name.first
});
console.log('----- ', this.setState.selected);
});
}
handleChange(value) {
this.setState({ selected: (value) });
}
renderOptions() {
return this.state.data.map((dt, i) => {
return (
<MenuItem
key={i}
value={dt.name.first}
primaryText={dt.name.first} />
);
});
}
render() {
return (
<div>
<DropDownMenu value={this.state.selected} onChange={this.handleChange}>
{this.renderOptions()}
</DropDownMenu>
</div>
);
}
}
Any help will be very welcome!
Thanks in advance..
In material UI dropdown, the selected value appears as third argument. So use something like this for your handleChange method
handleChange(event, index, value) {
this.setState({ selected: (value) });
}
Ref: http://www.material-ui.com/#/components/dropdown-menu#properties

Categories

Resources