I have a small React app where you can click on a piece of text to replace it with an input field, to allow you to edit it.
The problem is that the EditableText component does not seem to want to display the editingText state in the input field.
See example here: https://codesandbox.io/s/distracted-ellis-kyrdb
For posterity, the code for the main app is:
export default function App() {
const [cats, setCats] = useState([
{
id: 1,
name: "fred"
},
{
id: 2,
name: "jim"
}
]);
const [activeCat, setActiveCat] = useState({});
const handleClick = (cat, e) => {
setActiveCat(cat);
};
const saveName = (newName) => {
// save this cat's new name...
};
return (
<div className="App">
<div className="catMenu">
{cats.map((row) => (
<div
className="catItem"
key={row.id}
onClick={(e) => handleClick(row, e)}
>
{row.name}
</div>
))}
</div>
<div className="activeCat">
<h2>
<EditableText text={activeCat.name} saveText={saveName} />
</h2>
</div>
</div>
);
}
and for the EditableText component:
const EditableText = (props) => {
const { text, saveText } = props;
const [editing, setEditing] = useState(false);
const [editingText, setEditingText] = useState(text);
useEffect(() => {
const handleKeydown = (e) => {
if (editing) {
if (e.keyCode === 13) {
// Enter key pressed
saveText(editingText);
setEditing(false);
}
if (e.keyCode === 27) {
// Escape key pressed
setEditingText(text);
setEditing(false);
}
}
};
window.addEventListener("keydown", handleKeydown);
return () => {
window.removeEventListener("keydown", handleKeydown);
};
}, [text, saveText, editing, editingText]);
return (
<div>
<div
onClick={() => setEditing(true)}
className={`et-${editing ? "hidden" : "active"}`}
>
{text}
</div>
<input
type="text"
value={editingText}
onChange={(e) => setEditingText(e.target.value)}
className={`et-input-${editing ? "active" : "hidden"}`}
/>
</div>
);
};
export default EditableText;
Add another useEffect to listen to text prop change, you only use it as initial empty value:
useEffect(() => {
setEditingText(text);
}, [text]);
Related
Please help me! Delete Icon is not functional, when I click on delete icon it delete all the contact, on refreshing, it returns all the previous contacts. I am also using localStorage.
I have added all the Component of the React App Project.
App.js
import { v4 as uuid } from "uuid";
const App = () => {
const LOCAL_STORAGE_KEY = "contacts";
const [contacts, setContacts] = useState([]);
const addContactHandler = (contact) => {
console.log(contact);
setContacts([...contacts, { id: uuid(), ...contact }]);
};
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
};
useEffect(() => {
const retrieveContacts = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY)
);
if (retrieveContacts) {
setContacts(retrieveContacts);
}
}, []);
useEffect(() => {
if (contacts.length) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}
}, [contacts]);
return (
<>
<div className="app">
<Header />
<AddContact addContactHandler={addContactHandler} />
<ContactList contacts={contacts} getContactId={removeContactHandler} />
</div>
</>
);
};
export default App;
ContactList.js
const ContactList = (props) => {
const deleteContactHandler = (id) => {
props.getContactId(id);
};
const renderContactList = props.contacts.map((contact) => {
return (
<>
<ContactCard
contact={contact}
clickHandler={deleteContactHandler}
key={contact.id}
/>
</>
);
});
return (
<>
<div className="contactList">
<h2 className="contactList__title">Contact List</h2>
<div className="contactList__container">
{renderContactList}
</div>
</div>
</>
);
};
ContactCard.js
const ContactCard = (props) => {
const { id, name, email } = props.contact;
return (
<>
<div className="contactCard">
<div className="contactCard__contact">
<img
className="contactCard__userIcon"
src={userIcon}
alt="user-icon"
/>
<div className="contactCard__userName">
<h2>{name}</h2>
<p>{email}</p>
</div>
</div>
<div className="contactCard__delIcon">
<img
src={delIcon}
alt="del-icon"
onClick={() => props.clickHandler(id)}
/>
</div>
</div>
</>
);
};
export default ContactCard;
I have researched out the references. Unable to get the Solution.
The effect to store the contacts do not save empty arrays.
Thats why you get the old array after refreshing your page.
Just remove the condition.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);
But you should consider to remove this effect.
Save the contacts directly after setting the state instead.
const addContactHandler = (contact) => {
console.log(contact);
const newContactList = [...contacts, { id: uuid(), ...contact }];
setContacts(newContactList);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newContactList));
};
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newContactList));
};
So I was trying to update the value I got by the Addlist and I tried this but this isn;t working. Also when I click on the '+' button without writing anything, an empty list is created. How should I stop it. I've attached a code below.
import React from "react";
import "./App.css";
import { useState } from "react";
import TodoList from "./components/TodoList";
function App() {
const [input, setInput] = useState("");
const [list, setList] = useState([]);
const updateList = (e) => {
setInput(e.target.value);
};
const AddList = () => {
console.log("value added")
setList((addValue) => {
return [...addValue, input];
});
setInput("");
};
const updateItems=(id)=>{
const newValue=[...list].map((newVal)=>{
if(input.id===id){
input.text='';
}
return newVal;
})
setList(newValue);
}
const deleteItems = (id) => {
console.log("deleted");
setList((addValue) => {
return addValue.filter((element, index) => {
return index !== id;
});
});
};
return (
<div className="todo-app">
<h1> Enter Anything</h1>
<input
type="text"
placeholder="Add anything"
value={input}
onChange={updateList}
/>
<button onClick={AddList}>+</button>
<ul>
{list.map((itemsvalue, id) => {
return (
<TodoList
itemsValue={itemsvalue}
key={id}
onSelect={deleteItems}
id={id}
onUpdate={updateItems}
/>
);
})}
</ul>
</div>
);
}
export default App;
Any kind of help would be appreciated. Also if I want to split this into multiple components is there a way to do.
When user clicks on the add button there is the check for empty String AddList method
for ex:- User updates second index value, second position value will get updated.
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [index, setIndex] = useState(null);
const updateList = (e) => {
setInput(e.target.value);
};
useEffect(() => {
setList(list);
console.log(list, '<>?');
}, [index]);
const AddList = () => {
if (input.trim() !== '') {
setList([...list, input]);
}
setInput('');
};
const updateValue = (index) => {
console.log(list[index]);
setIndex(index);
if (list[index].trim() !== '') {
setInput(list[index]);
}
};
const UpdateList = () => {
list[index] = input;
console.log(list, 'before <>?');
setIndex(null);
setInput('');
};
return (
<div>
<input type="text" placeholder="Add anything" value={input} onChange={updateList} />
<button disabled={!index && !list.length === 0} onClick={AddList}>
Add
</button>
<button disabled={input.trim() === ''} onClick={UpdateList}>
Update
</button>
{list.map((m, index) => (
<h1 style={{ border: '1px solid black' }} onClick={() => updateValue(index)}>
{m}
</h1>
))}
</div>
);
I have one button in front each list item when I click on the watched button, I want the text of the button to be changed to not watched(or when click on not watched it change to watched) and to be included in the watched list. At the top, I have three buttons, watched and not watched , which one I clicked on. Show me the list of the movies that I changed, their state and for third button(with text of all )it shows the whole list.I think that my problem is handleWatchedBtn function . this is picture of project maybe it is simple my explanation! thank you for your help.
import React, { useEffect, useState } from "react";
const App = () => {
const [Movies, setMovie] = useState([]);
const [Loading, setLoading] = useState(true);
const [Keyword, setKeyword] = useState("");
const [OverSeven, setOverSeven] = useState(false);
const [filterByWatch, setfilterByWatch] = useState("ALL");
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
setMovie(
response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false,
}))
);
setLoading(false);
});
}, []);
function handleWatchedBtn(id) {
setMovie(() =>
Movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
})
);
}
function handleWatchedChange(filter) {
setfilterByWatch({ filterByWatch: filter });
}
function handleKeywordChange(e) {
setKeyword(e.target.value);
}
function handleOverSevenChange(e) {
setOverSeven(e.target.checked);
}
function filterItems() {
return Movies.filter((item) =>
item.name.toLowerCase().includes(Keyword.toLowerCase())
)
.filter((item) => (OverSeven ? item.rate > 7 : true))
.filter((item) =>
filterByWatch === "ALL"
? true
: item.watched === (filterByWatch === "WATCHED")
);
}
if (Loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input type="text" value={Keyword} onChange={handleKeywordChange} />
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
not watch
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={OverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filterItems().map((movie) => (
<li data-id={movie.id}>
{`${movie.name} ${movie.rate}`}{" "}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "Watched" : " Not watched"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;
You could introduce a new array state which stores all filtered movies and is updated every time the movies or the filter are updated.
You can then pass its reference to the map function that generates the list.
Also, notice that I've added the spread operator (...) in the handleWatchedBtn function and adjusted the handleWatchedChange method to update the state to a string and not an object.
Try to change your code like this:
import React, { useEffect, useState } from "react";
const App = () => {
const [Movies, setMovie] = useState([]);
const [filteredMovies, setFilteredMovies] = useState([]);
const [Loading, setLoading] = useState(true);
const [Keyword, setKeyword] = useState("");
const [OverSeven, setOverSeven] = useState(false);
const [filterByWatch, setfilterByWatch] = useState("ALL");
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
const newMovies = response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false,
}));
setMovie(newMovies);
setLoading(false);
});
}, []);
useEffect(() => {
// Update filtered movies when the data or the filter changes
const newFilteredMovies = Movies.filter((item) =>
item.name.toLowerCase().includes(Keyword.toLowerCase())
)
.filter((item) => (OverSeven ? item.rate > 7 : true))
.filter((item) =>
filterByWatch === "ALL"
? true
: item.watched === (filterByWatch === "WATCHED")
);
setFilteredMovies(newFilteredMovies);
}, [Movies, filterByWatch]);
function handleWatchedBtn(id) {
setMovie(() =>
Movies.map((movie) => {
if (movie.id === id) {
// Add the spread operator here
return { ...movie, watched: !movie.watched };
}
return movie;
})
);
}
function handleWatchedChange(filter) {
// Change this line
setfilterByWatch(filter);
}
function handleKeywordChange(e) {
setKeyword(e.target.value);
}
function handleOverSevenChange(e) {
setOverSeven(e.target.checked);
}
if (Loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input type="text" value={Keyword} onChange={handleKeywordChange} />
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
not watch
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={OverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filteredMovies.map((movie) => (
<li data-id={movie.id}>
{`${movie.name} ${movie.rate}`}{" "}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "Watched" : " Not watched"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;
I want to add startClick to BackButton as in NextButton. In other words, when the BackButton is clicked, the startClick function should work first, then the dispatch (giveForm2PreviousStep (props.currentStep, props.goToStep)) method should work in order. How can I do that?
Question JS
const Question = props => {
const dispatch = useDispatch()
const loading = useSelector(state => state.app.isLoading)
const error = useSelector(state => state.app.error)
const reduxF2 = useSelector(state => state.app.forms.f2)
const [input, setInput] = useState({
value: reduxF2.PastReceivables.value,
valid: true,
})
const changeSelected = val => {
setInput({ ...input, value: val })
}
useEffect(() => {
setInput({ ...input, value: reduxF2.PastReceivables.value })
}, [reduxF2.PastReceivables.value])
useEffect(() => {
if (reduxF2.bulkSaved && props.currentStep === 2) {
dispatch(giveForm2NextStep(props.currentStep, props.goToStep))
dispatch(resetForm2SavedStatus())
}
}, [reduxF2.bulkSaved])
const startClick = e => {
if (input.value === null || input.value === '') {
setInput({ ...input, valid: false })
} else {
setInput({ ...input, valid: true })
const questions = getPastReceivablesArray('PastReceivables', input.value, reduxF2)
if (questions.length == 0) {
dispatch(giveForm2NextStep(props.currentStep, props.goToStep))
} else {
dispatch(updateForm2(questions))
}
}
}
return (
<>
<MyProgressBar now='8' />
<Question>Question here</Question>
<QuestionForm>
<NumericInput
valid={input.valid}
onChange={changeSelected}
value={input.value}
/>
</QuestionForm>
<div className='d-flex justify-content-between'>
<BackButton onClick={() => dispatch(giveForm2PreviousStep(props.currentStep, props.goToStep))} />
<NextButton onClick={startClick} loading={loading} />
</div>
<Warning error={error} />
</>
)
}
BackButton JS
const BackButton = ({ text = 'Back', onClick = null, loading = false, width = '7.5rem' }) => {
return (
<Button
variant='secondary'
className='back-button'
onClick={onClick}
disabled={loading}
style={{width}}
>
<MySpinner loading={loading} />
{!loading && <>{text}</>}
</Button>
)
}
You can call multiple functions in onClick event like below
<BackButton
onClick={(e) => {
startClick(e);
dispatch(giveForm2PreviousStep(props.currentStep, props.goToStep))
}}
/>
You can call multiple function in onclick or else you can send call backs to startclick.
so the call backs will be executed after startclick.
easy to give all the fucntions in onClick itself.
I'm trying to add a whole array to useState variable
import React, { Fragment, useState, useEffect } from 'react';
import { Form, Button, Popover, OverlayTrigger } from 'react-bootstrap';
const Filter = props => {
const [formData, setFormData] = useState({
filter: ''
});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci","paris", "london"];
console.log(retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const { filter } = formData;
const onChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
props.filterData(filter);
//Close the Popover
document.body.click();
};
const popover = (
<Popover id="popover-basic">
<Form>
<Form.Group controlId="formGroupEmail">
<Form.Label>Add New Filter</Form.Label>
<Form.Control
type="text"
placeholder="New Filter"
name="filter"
onChange={e => onChange(e)}
/>
</Form.Group>
<Button variant="dark" type="submit" onClick={e => onSubmit(e)}>
Add
</Button>
</Form>
</Popover>
);
return (
<Fragment>
<label>
<p className="filter-title">{props.title}</p>
</label>
<div className={props.className ? props.className : 'filter'}>
{!props.retrievedFilter
? items.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})
: props.retrievedFilter.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})}
<OverlayTrigger
trigger="click"
placement="right"
rootClose
overlay={popover}
>
<p className="text-field">Type new one</p>
</OverlayTrigger>
</div>
</Fragment>
);
};
export default Filter;
however retrievedItems shows as an empty array in the console.
any help would be appreciated.
setState is async. You have to console.log inside an effect hook with the array as a parameter.
useEffect(() => console.log(retrieved_items), [ retrievedItems ])
The second parameter ensures that the effect fires in repose to a change in the values passed to it.
Per my comment, here is a code snippet that I think does what you want.
I couldn't get it running in SO but here's a codepen: https://codepen.io/anon/pen/PrYYmz?editors=1010 (watch the chrome console as you add items)
import React, {
Fragment,
useState,
useEffect
} from 'react';
const Filter = props => {
const [formData, setFormData] = useState({filter: ''});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci", "paris", "london"];
console.log('add', retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const {filter} = formData;
const onChange = e => {
setFormData({ ...formData,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
//props.filterData(filter);
//Close the Popover
document.body.click();
};
return (
<Fragment >
<label >
<p className = "filter-title" > {
props.title
} </p> </label> <
div className = {
props.className ? props.className : 'filter'
} > {!props.retrievedFilter ?
items.map((item, index) => {
return ( <
div className = "filter-text"
key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)}>X</button></div>
);
}) :
props.retrievedFilter.map((item, index) => {
return ( <div className = "filter-text" key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)} >X</button></div>);})} <input type = "text" placeholder = "New Filter" name = "filter" onChange = {e => onChange(e) }/>
<button variant = "dark" type = "submit" onClick = {e => onSubmit(e)} >Add</button>
</div>
</Fragment>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>