Changing the order of an item in a list using ReactJS - javascript

I have created a list using material UI and reactjs, and when a new element is added to the list the new element will go on top of the list.
I have a requirement where when I click on an element on the list the element should be strike-through and that now that element should be listed on the bottom of the list.
I was able to strike-through the element when clicked, but I am confused as to how to bring the element to the bottom of the list
How should I approach this problem?
The code of the listlayout.js is presented here, In this code, the added items are listed, and I need to find the way to change the list order when an element is stricked
app.js
class App extends Component {
constructor(props) {
super(props);
this.state={
items:[],
newItem:{
id:'',
itemText:''
},
updateItem:false
};
this.handleInput = this.handleInput.bind(this);
this.addItem = this.addItem.bind(this);
}
handleInput = e =>{
this.setState({
newItem:{
id:1 + Math.random(),
itemText: e.target.value
}
});
};
addItem = e =>{
e.preventDefault();
const typedItem = this.state.newItem;
if(typedItem.itemText !==""){
const typedItems=[...this.state.items,typedItem];
this.setState({
items:typedItems,
newItem:{
id:'',
itemText: ''
},
updateItem:false
})
}
};
render() {
return (
<div >
<HeaderBar/>
<ListLayout items={this.state.items}
/>
</div>
);
}
}
export default App;
ListLayout.js
const ToDoList = props => {
const clearList = props.clearList;
const deleteItem = props.deleteItem;
const updateItem = props.updateItem;
const strikeList = props.strikeList;
const listItems = item => {
return <div key={item.id}>{item.itemText}</div>;
};
const completed = id => {
document.getElementById(id).style.textDecoration = "line-through";
return true;
};
const strikeTextMethod = id => {
completed(id);
};
return (
<div>
<Grid container justify="center" alignContent="center">
<Grid item xs={12} md={6}>
<Typography variant="h6" className={classes.title}>
To do List
</Typography>
<div className={classes.demo}>
<List dense={dense}>
{items
.slice(0)
.reverse()
.map(x => (
<ListItem
key={x.id}
button
id={x.id}
onClick={() => strikeTextMethod(x.id)}
divider
>
<ListItemText primary={listItems(x)} />
<ListItemSecondaryAction></ListItemSecondaryAction>
</ListItem>
))}
</List>
</div>
</Grid>
</Grid>
<br />
</div>
);
};
export default ToDoList;

You have to mainatain the strike event for each item in the array. You can add an additional property to the array items, like isStriked or status.. something like that.
Then you can sort them accordingly..

Your code doesn't seem to be the entire solution. I don't see the definition of items as an example.
but something like this could be a workaround.
const ToDoList = props => {
const [items, setItems] = React.useState(props.items || []); // Initial values
// Maybe you need to these lines to sync the items state.
React.useEffect(
() => {
setItems(items)
},
[props.items]
)
const completed = id => {
document.getElementById(id).style.textDecoration = "line-through";
return true;
};
const strikeTextMethod = id => {
const index = items.findIndex(x => x.id === id);
const newItems = [items[index], ...items.slice(0, index - 1), ...items.slice(index + 1)]
setItems(newItems);
completed(id);
};
return (
)
}

Related

TextInput gets unfocused after typing each character

I'm using React to build a form and I'm trying to filter a list with the SearchInput (which works the same as TextInput) located in the child component Header. But everytime I type a character the SearchInput gets unfocused
function index() {
const list = [//data\\]
const [search, setSearch] = useState("");
const [filteredResults, setFilteredResults] = useState([]);
const searchItems = (searchValue) => {
setSearch(searchValue);
if (search !== "") {
const filteredData = partners.filter((item) => {
return Object.values(item)
.join("")
.toLowerCase()
.includes(search.toLowerCase());
});
setFilteredResults(filteredData);
} else {
setFilteredResults(partners);
}
};
const Header = () => (
<Box>
<SearchInput
placeholder="Search"
value={search}
onChange={(e) => searchItems(e.target.value)}
/>
</Box>
);
return (
<Parent
headerContent={<Header />}
>
<Box>
<Table data={search.length > 1 ? filteredResults : list} />
</Box>
</Parent>
);
}
export default index;
Oh, I think I can see the problem now - it's the way you're rendering the <SearchInput /> component. You're inadvertantly creating a new functional component on every render. Either inline the Header directly into the Parent control's headerContent property, or create an entirely separate component:
const Header = ({ search, onSearchChange }) => {
const handleChange = (e) => onSearchChange(e.target.value);
return (
<Box>
<SearchInput
placeholder="Search"
value={search}
onChange={handleChange}
/>
</Box>
);
}
function index() {
// ----- 8< -----
return (
<Parent
headerContent={<Header search={search} onSearchChange={searchItems} />}
>
{/* ... */}
</Parent>
);
}
While you're there, you have a subtle bug with your comparison - it looks like you're searching your partners effectively as a list of strings; but, since you're joining them, if you had partners with the names:
'one'
'two'
You're creating a search string as 'onetwo' - so searching for 'et' would match, even though you don't actually have a partner matching that. You can fix that by just checking each partner individually... something like:
const searchItems = (searchValue) => {
setSearch(searchValue);
if (search !== "") {
const searchValueLower = searchValue.toLowerCase();
const filteredData = partners.filter((item) => {
return Object.values(item)
.some(item => item.toLowerCase().includes(searchValueLower);
});
setFilteredResults(filteredData);
} else {
setFilteredResults(partners);
}
};

filtering object of array by id - REACT

I'm having a big struggle with filtering an object of an array of objects by its ID in React. Let me explain:
The App is a Notes app, that stores every note you create with its Title, Text(name) and created date. The key is the ID.
Now I'm trying to create a popup modal every time I click on a note, which I managed to do ok, except for one thing: when the modal appears, it doesn't show only the selected note but all the notes list. I've tried with different array methods to filter the note I need, but didn't succeed.
This is the App.js file:
import React, { useState } from 'react';
import './App.css';
import Form from './components/Form';
import List from './components/List';
import { v4 as uuidv4 } from 'uuid';
import Modal from 'react-modal';
import ModalList from './components/ModalList';
Modal.setAppElement('#root');
function App() {
/*HOOKS */
const [list, setList] = useState([]);
const [modalList, setModalList] = useState([]);
//for modal:
let subtitle;
const [modalIsOpen, setIsOpen] = React.useState(false);
/*FUNCTIONS */
//add new notes
function handleAdd(title, name) {
if (name) {
const newList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setList(newList);
console.log(newList);
const newModalList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setModalList(newModalList);
}
else { alert("You should complete the notes field") }
}
//get the date for adding the note
function getCurrentDate() {
let newDate = new Date()
let date = newDate.getDate();
let month = newDate.getMonth() + 1;
let year = newDate.getFullYear();
let hours = newDate.getHours();
let minutes = newDate.getMinutes();
return `${month < 10 ? `0${month}` : `${month}`}/${date}/${year}, ${hours}:${minutes < 10 ? `0${minutes}` : `${minutes}`} hs.`
}
//deleting a note
function del(x) {
if (window.confirm("Do you really want to delete this item? The action is permanent.")) {
const newList = list.filter((item) => item.id !== x);
setList(newList);
}
}
//opening a modal
function openModal() {
setIsOpen(true);
}
//after opening a modal
function afterOpenModal() {
// references are now sync'd and can be accessed.
subtitle.style.color = '#f00';
}
//closing a modal
function closeModal() {
setIsOpen(false);
}
/*APP */
return (
<>
<div>
{/* MODAL */}
<Modal
isOpen={modalIsOpen}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Example Modal"
>
{modalList.map((item) => { return <ModalList key={item.id} item={item} quit={closeModal} /> })}
</Modal>
</div>
{/* FORM */}
<div className='App'>
<Form handleNew={handleAdd} />
</div>
{/* NOTES LIST */}
<div className='notes-list'>
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
</div>
</>
);
}
export default App;
And this is the ModalList.jsx file:
const ModalList = (props) => {
const { item, quit} = props;
/*LIST */
return (
<li ><button className='delete' onClick={()=>quit(item.id)}>x</button><p className='note-title'>{item.title}</p><p>{item.date}</p><p className='note-name'>{item.name}</p> </li>
);
}
export default ModalList;
I know I have to someway filter the object by its ID so that only appears what I clicked and not all the existing elements in the list, but I'm not finding the way.
Thanks a lot!
You are using Array.map here which is doing what it's supposed to do (listing the items), instead you should be using Array.filter which would return the ModalItem you need
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
openModal needs pass the item you clicked as a parameter and pass it to the callback.
Something like:
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={() => openModal(item)} /> })}
Then openModal function needs to pass that parameter to the Modal component. To achieve that you can store it in your modalList for instance via setModalList([item])

Add to favourites and view from favourites with React Hooks?

I have a state
const [ideas, setIdeas] = useState([{title:"test", favourite:false]);
Component Idea.jsx returns props.title and a button "fav".
App.jsx maps through the idea[] and renders each idea.title in
<Item title = {idea.title}/>
on the page.
Problem:
Every time when "fav" is clicked I want to toggle ideas[index].favourite.
How to change a value of favourite only for an idea that was clicked?
How to add this exact idea to the array favourites[]?
App.jsx
function App() {
const [ideas, setIdeas] = useState([{title:"test",
favourite:false}]);
const [isClicked, setIsClicked] = useState(false)
function showAllIdeas () {
setIsClicked(prevValue => {
return !prevValue
}
)
}
function mapIdeas(){return ideas.map((ideaItem, index) => {
return (<Idea
key = {index}
id = {index}
title = {ideaItem.title}
/>
);
})}
return ( <div>
<Fab color="primary" onClick={showAllIdeas}>{expandText()}</Fab>
{isClicked && mapIdeas()}
</div>)
}
Item.jsx
function Idea(props) {
const [isClicked, setIsClicked] = useState(false)
function handleClick(){
setIsClicked(prevValue => {
return !prevValue
})
}
console.log(isClicked)
return(
<div className={"idea-list" } ><p>{props.title} {isClicked ?
<StarIcon onClick={handleClick}/> :<StarBorderIcon onClick=.
{handleClick}/>}</p>
</div>
)
}
const handleFavToggle = (index) => {
setItems(items=> {
const data = [...items]
data[index] = {...data[index],favourite: !data[index].favourite }
return data
})
}
<Item key={index} title={item.title} index={index} handleFavToggle={handleFavToggle}/>
In item component you have to handle click with handleFavToggle and pass all params

How to conditionally update react list components

I have the React app below (jsfiddle):
const ListItem = (props) => {
return (
<div className={props.active ? "active" : ""}>Item {props.index}</div>
)
}
const initialItems = ["item1", "item2", "item3", "item4", "item5"]
const App = (props) => {
const [activeIndex, setActiveIndex] = React.useState(0);
const goUp = () => {
if(activeIndex <= 0) return;
setActiveIndex(activeIndex - 1);
}
const goDown = () => {
if(activeIndex >= initialItems.length - 1) return;
setActiveIndex(activeIndex + 1);
}
return (
<div>
<p>
<button onClick={goUp}>Up</button>
<button onClick={goDown}>Down</button>
</p>
<div>
{initialItems.map((item, index) => (
<ListItem active={index === activeIndex} index={index} key={index} />
))}
</div>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
Using buttons you can highlight the current list element. The issue with the current approach is that on every active index change it re-renders the full list. In my case, the list might be very big (hundreds of items) with a more complicated layout, which introduces performance problems.
How might this code be modified so it updates only specific list item components and doesn't trigger re-render of all others? I'm looking for a solution without third-party libraries and without direct DOM manipulations.
You can wrap ListItem with React.memo() as here.
This is your ListItem component,
const ListItem = (props) => {
return (
<div className={props.active ? "active" : ""}>Item {props.index}</div>
)
};
By using React.Memo(),
const ListItem = React.memo((props) => {
return (
<div className={props.active ? "active" : ""}>Item {props.index}</div>
)
});
In this case ListItem is only rendered when props gets changed.
See for updated JsFiddle and check with console.log() s.

React rendering deleted array element after setState()

I'm having an issue where React seems to be rendering a deleted array element after I've removed it from an array in the components state. After setState(), rendering is triggered, but the deleted item is shown, instead of the remaining item (see GIF video below).
Although the component is extremely simple and I've spent hours on this problem, I haven't been able to solve this issue. The strange thing is that the newState object actually contains the valid new list, but it's not rendered.
I really hope someone can help me figure this out!
import React from "react";
import Button from "#material-ui/core/Button";
import update from "immutability-helper";
import Grid from "#material-ui/core/Grid";
import TextField from "#material-ui/core/TextField";
import * as R from "ramda";
class SessionNoteGroup extends React.Component {
state = {
id: this.props.id,
notes: this.props.notes
};
render() {
const { classes } = this.props;
const { id, notes } = this.state;
return (
<div>
<Grid container>
<Grid item xs={12}>
<TextField
multiline
fullWidth
id="notes"
name="notes"
label="Notes"
rows="2"
value={notes}
onChange={this.handleValueChange}
/>
</Grid>
</Grid>
<Button variant="outlined" color="primary" onClick={this.handleDelete}>
Delete
</Button>
</div>
);
}
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
let newState = {
id: id,
notes: value
};
this.setState(newState);
};
handleDelete = () => {
this.props.onDelete(this.state.id);
};
}
class SessionNotes extends React.Component {
state = {
notes: this.props.notes.slice(),
deleted: []
};
next_id = 2;
createNotes = () => {
let notesList = [];
for (let i = 0; i < this.state.notes.length; i++) {
const { id, notes } = this.state.notes[i];
notesList.push(
<SessionNoteGroup
id={id}
notes={notes}
onDelete={this.handleDelete}
index={i + 1}
/>
);
}
console.log(notesList);
return notesList;
};
handleDelete = id => {
const newNotes = R.filter(note => note.id !== id, this.state.notes);
this.setState({ notes: newNotes });
};
handleClickAdd = async () => {
const note = {
id: this.next_id,
notes: ""
};
this.next_id++;
const newState = update(this.state, { notes: { $push: [note] } });
this.setState(newState);
};
render() {
return (
<div>
{this.createNotes()}
<Button
variant="outlined"
color="primary"
onClick={this.handleClickAdd}
>
Add
</Button>
</div>
);
}
}
export default SessionNotes;
Few things.
When you want to set the state, based on the prev state, use the setState with a callback, which takes as argument the prevState. So the next code:
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
let newState = {
id: id,
notes: value
};
this.setState(newState);
};
Will be something like:
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
this.setState(prevState => ({ id: prevState.id, notes: value}));
};
Same in the below component:
handleDelete = id => {
const newNotes = ;
this.setState(prevState => ({ notes: R.filter(note => note.id !== id, prevState.notes) }));
};
And so on for all the times that you update the state based on previous state value.
Then when you do create a list of elements in react, use key property:
<SessionNoteGroup
key={id}
id={id}
notes={notes}
onDelete={this.handleDelete}
index={i + 1}
/>
That's used by react for managing the render of list of items
Try adding a key to the container div of your render
return (
<div key = {this.props.id}>
<Grid container>
<Grid item xs={12}>
<TextField
multiline
fullWidth
id="notes"
name="notes"
label="Notes"
rows="2"
value={notes}
onChange={this.handleValueChange}
/>
</Grid>
</Grid>
<Button variant="outlined" color="primary" onClick={this.handleDelete}>
Delete
</Button>
</div>
);

Categories

Resources