State not updating in different component - javascript

I have a searchbar component that I used context to import into another component. The state of the searchbar in its own component works but when I use the context to import it to another component it does not work. I have used other contexts in my project and they have worked but the searchbar state doesn't. I have no idea where to start, or how to go about fixing it. Can someone point me in the right direction?
export const SearchInput = () => {
const [searchInput, setSearchInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault()
}
return (
<div>
<form onSubmit={handleSubmit}>
<input type='text'
className='search-input'
name='search-movies'
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)} />
</form>
</div>
)
}
//Use Context Component
export const SearchContext = React.createContext()
export function SearchProvider({ children }) {
const [searchInput, setSearchInput] = useState('');
const value = {
searchInput
}
return (
<div>
<SearchContext.Provider value={value}>
{children}
</SearchContext.Provider>
</div>
)
}
const Movies = () => {
const { data, loading, isErr } = useFetch([
`https://api.themoviedb.org/3/list/7077601?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`,
`https://api.themoviedb.org/3/list/7078334?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`,
`https://api.themoviedb.org/3/list/7078244?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`
]);
const { watchList, handleClick } = useContext(WatchListContext);
const { searchInput } = useContext(SearchContext)
const [moviePoster, setmoviePoster] = useState(`giphy (1).gif`);
const [movieTitle, setmovieTitle] = useState('');
const [movieDescription, setmovieDescription] = useState('')
const styles = {
backgroundImage: `url(${moviePoster})`
};
SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]);
return (
<div className='movie-container'>
{isErr && <div className="error">{isErr}</div>}
{loading && <Spinner animation="border" variant="secondary" className="spinner" >
<span>Loading...</span>
</Spinner>}
<div className='movie-hero' style={styles}></div>
<div className="contains-descriptions">
<h2 className="hero-movie-title show-movie">{movieTitle}</h2>
<p className="hero-movie-description show-movie">{movieDescription}</p>
</div>
<section className="movies">
<h2 style={{ color: 'white', marginLeft: '20px' }}>Action </h2>
{data && <Swiper
spaceBetween={10}
slidesPerView={6}
pagination={{ clickable: true }}
scrollbar={{ draggable: true }}
onSlideChange={() => console.log('slide change')}
onSwiper={(swiper) => console.log(swiper)}
>
{data && data[0].items.map(movie =>
<SwiperSlide key={movie.id}>
<div className='movie' >
<img onMouseOver={() => {
setmoviePoster(`${"https://image.tmdb.org/t/p/original" + movie.poster_path}`);
setmovieTitle(movie.original_title);
setmovieDescription(movie.overview);
}}
src={'https://image.tmdb.org/t/p/original' + movie.poster_path} width='250' height='300'
alt='Promotional Poster For Movie'
/>
<button className="watchlist-btn"
onClick={() => handleClick(movie.original_title)}>
{watchList.includes(movie.original_title) ?
<i className="fas fa-minus-circle"></i> :
<i className="fas fa-plus-circle"></i>}
</button>
</div>
</SwiperSlide>
)
}
</Swiper>}
</section>

I'm assuming a component tree that looks something like this:
+-- SearchProvider
| +-- SearchInput
| +-- Movies
Your SearchProvider should be providing both the state and the state setter as its value:
export const SearchContext = React.createContext()
export function SearchProvider({ children }) {
const [searchInput, setSearchInput] = useState('');
const value = {
searchInput,
setSearchInput
};
return ...
}
Your SearchInput should no longer be controlling its own local state. That state now has to be shared with the rest of the tree. Instead, it subscribes to the context and updates it directly:
export const SearchInput = () => {
const { searchInput, setSearchInput } = React.useContext(SearchContext);
const handleSubmit = (e) => {
e.preventDefault()
};
return ...
}

Why are you using context and not just useState and props?
I think it would work with something like the following:
export const SearchInput = (props) => {
const handleSubmit = (e) => {
e.preventDefault()
}
return (
<div>
<form onSubmit={handleSubmit}>
<input type='text'
className='search-input'
name='search-movies'
value={props.value}
onChange={(e) => props.onChange(e.target.value)} />
</form>
{props.children}
</div>
)
}
export function SearchCompoent({ children }) {
const [searchInputValue, setSearchInputValue] = useState('');
return (
<SearchInput value={searchInputValue}>
{children}
</SearchInput>
)
}

Related

Warning: Cannot update a component (`Home`) while rendering a different component (`Posts`). To locate the bad setState() call inside `Posts`,

Problem: when i click on the button
<button
onClick={() => {
navigate('/posts');
setResponse(e.id);
}}
>
I get this error: Warning: Cannot update a component (Home) while rendering...
I think problem only in this line
navigate('/posts');
because if I delete it, error disappears
full code under without import
App.js
function App() {
const [response, setResponse] = useState({});
return (
<Router>
<Routes>
<Route exact path="/" element={<Home setResponse={setResponse} />} />
<Route exact path="/posts" element={<Posts response={response} />} />
<Route exact path="*" />
</Routes>
</Router>
);
}
export default App;
Home.js
function Home({ setResponse }) {
const [modal, setModal] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const state = useSelector((state) => state);
console.log(state);
if (state.user.isLoading) {
return <h1>Loading...</h1>;
}
const toggleModal = () => {
setModal(!modal);
};
return (
<div className="App">
<button onClick={(e) => dispatch(fetchUsers())}>Fetch users</button>
{state.user.data &&
state.user.data.map((e) => (
<div key={e.id}>
<li key={e.name}>{e.name}</li>
<button
key={e.id + 10}
onClick={() => {
navigate('/posts');
setResponse(e.id);
}}
className="btn"
>
Posts
</button>
<button onClick={toggleModal} key={e.id + 100} className="bnt">
Albums
</button>
</div>
))}
<Albums modal={modal} setModal={setModal} />
</div>
);
}
export default Home;
Posts.js
function Posts({ response }) {
const dispatch = useDispatch();
const navigate = useNavigate();
const state = useSelector((state) => state);
console.log(state);
if (state.post.isLoading) {
return <h1>Loading...</h1>;
}
if (!state.post.data) {
dispatch(fetchPosts());
}
return (
<div className="App">
Posts
{state.post.data &&
state.post.data
.filter((e) => e.userId === response)
.map((e) => (
<div key={e.userId.toString() + e.id.toString()}>
<li key={e.id}>{e.title}</li>
</div>
))}
<button
onClick={() => {
navigate('/');
}}
>
List of users
</button>
</div>
);
}
export default Posts;
I tried to use useEffect(), but it doesn't work in my case
<button
onClick={() => {useEffect(()=>{
navigate('/posts');
setResponse(e.id);},[])
}}
>
If you're navigating away to another page, you shouldn't be updating the state as you're navigating (which you're doing by calling setResponse after navigate).
To fix this error, you'd have to call navigate() after React finishes updating the response variable, which you can do by using a useEffect call at the top-level of your Home component:
// you should also pass the value of response to the Home component so it knows when it's been changed
function Home({ setResponse, response }) {
const [modal, setModal] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const state = useSelector((state) => state);
console.log(state);
useEffect(() => {
// This if statement checks if response === {}, which is a bit awkward; you should instead initialize your response state to a value like `null`
if (typeof response === 'object' && Object.keys(response).length === 0) {
return
}
navigate('/posts')
}, [response])
if (state.user.isLoading) {
return <h1>Loading...</h1>;
}
const toggleModal = () => {
setModal(!modal);
};
return (
<div className="App">
<button onClick={(e) => dispatch(fetchUsers())}>Fetch users</button>
{state.user.data &&
state.user.data.map((e) => (
<div key={e.id}>
<li key={e.name}>{e.name}</li>
<button
key={e.id + 10}
onClick={() => {
// navigate('/posts');
setResponse(e.id);
}}
className="btn"
>
Posts
</button>
<button onClick={toggleModal} key={e.id + 100} className="bnt">
Albums
</button>
</div>
))}
<Albums modal={modal} setModal={setModal} />
</div>
);
}
export default Home;

How Can I target single item by map button in React Typescript?

So I have a functional components here:
export default function Test() {
const [products, setProduct] = useState<any>([]);
const [image, setImage] = useState<any>([""]);
const [prices, setPrice] = useState<any>([]);
const [showPrice, setShowPrice] = useState<boolean>(false);
const [PhonePrice, setPhonePrice] = useState<any>("");
useEffect(() => {
async function loadProducts() {
const res = await fetch("http://raw.githubusercontent.com/reborn094/Practice/main/data.json", {
method: "GET",
});
const json = await res.json();
const data = json.products;
setProduct(data);
return (
<div>
{products.map((product: any, index: number) => {
return (
<Col>
<Card key={index} style={{ width: "20rem" }}>
<Card.Body>
<Card.Title>
</Card.Title>
<Card.Title>
<Child ProductImage={image[index]} name={product.name} code={product.code} onSelectProduct={()=>{
>
what should I write here????
------------------------
}}
></Child>
</Card.Title>
<Card.Text></Card.Text>
</Card.Body>
</Card>
</Col>
);
})}
</div>
);
}
And here is my Child components :
export default function Child(props:{
onSelectProduct?:()=> void;
}) {
return (
<div>
<Button onClick={props.onSelectProduct}></Button>
</div>
)
}
My question is What if I want to set button in Test components to target single item in list, what should I do? Because Now If I set Button that Button would trigger all item.What should I do in the function onSelectProduct?

Why is my ref always null even though I'm setting it to a component

So basically I have 2 pieces, the sidebar, then the opener. I'm trying to setup a ref that will connect the sidebar to the current opener. The opener is a functional component, and no matter what I do the current value is null. Am I missing something? I'm just trying to resize a component. My goal is to be able to resize the shown sidebar with the opener.
Here's part of the Render function.
render() {
const { selected, isSidebar, selectedType, search, active } = this.state;
const { pending, callback, resource } = this.props;
const pendingLengh = pending ? pending.length : 0;
const callbackLength = callback ? callback.length : 0;
const isResource = !resource || !Object.keys(resource).length;
return (
<div className="newPatientPage mainPage">
{this.renderMetadata()}
<SubTopBar
title="New Patient Processing"
noLeftSide={true}
subStatus={this.getStatus(pendingLengh, callbackLength)}
isBarcode={!isResource}
sideComponent={this.renderSideComponent()}
/>
{
active ?
<SnapshotSideBar
ref={this.sidebarRef}
patientResource={this.props.patientResource}
isShow={isSidebar}
settup={this.state.settup}
isScan={true}
handleCloseSidebar={this.handleCloseSidebar}
/> :
<NewPatientSideBar
ref={this.sidebarRef}
stepProps={this.state.stepProps}
selected={selected}
isShow={isSidebar}
handleCloseSidebar={this.handleCloseSidebar}
/>
}
<SidebarExtension sidebarToggle={this.toggleSidebar} sidebarReference={this.sidebarRef} sidebarState={isSidebar}/>
Here's the SidebarExtension component
const SidebarExtension = ({
sidebarToggle,
sidebarReference,
sidebarState,
...restProps
}) => {
const [xPos, setXPos] = useState(0);
const [width, setWidth] = useState();
const [openerPosition, setOpenerPosition] = useState(50);
const [isOpen, setIsOpen] = useState(false);
const toggleSidebar = () => {
sidebarToggle();
setIsOpen(!isOpen);
};
useEffect(() => {
setIsOpen(sidebarState);
}, [sidebarState])
if ((!isOpen && !sidebarState)) {
return (
<>
<div
className="resizeHandle"
style={{
right: "0Px",
}}
onClick={toggleSidebar}
>
<LeftCharvenSVG />
</div>
</>
);
}
return (
<>
<div
className="resizeHandle active"
onClick={toggleSidebar}
onMouseDown={startResize}
>
<LeftCharvenSVG />
</div>
</>
);
};
export default SidebarExtension;
Here's what the constructor looks like.
Main Constructor
From the docs https://reactjs.org/docs/forwarding-refs.html you need to wrap your functional component in React.forwardRef()
Example
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
In your case that would be:
const SidebarExtension = React.forwardRef(({
sidebarToggle,
sidebarReference,
sidebarState,
...restProps
}, ref) => {
const [xPos, setXPos] = useState(0);
const [width, setWidth] = useState();
const [openerPosition, setOpenerPosition] = useState(50);
const [isOpen, setIsOpen] = useState(false);
const toggleSidebar = () => {
sidebarToggle();
setIsOpen(!isOpen);
};
useEffect(() => {
setIsOpen(sidebarState);
}, [sidebarState])
if ((!isOpen && !sidebarState)) {
return (
<>
<div
className="resizeHandle"
style={{
right: "0Px",
}}
ref={ref}
onClick={toggleSidebar}
>
<LeftCharvenSVG />
</div>
</>
);
}
return (
<>
<div
className="resizeHandle active"
onClick={toggleSidebar}
onMouseDown={startResize}
>
<LeftCharvenSVG />
</div>
</>
);
});
export default SidebarExtension;

Why my code is not fetching updated details from API's even after changing state in React JS?

I am using two components: LandingPage and SearchMovie. SearchMovie component is updating searchTerm (onChange) and passing it to LandingPage(Parent component) which is fetching movies from API. I checked in console.log and SearchTerm state is updating but LandingPage is not re rendering with updated state of searchTerm. How can I do it? I am posting the code here:
**
LandingPage code:
**
import React, { useEffect, useState, useRef } from 'react'
import { Typography, Row } from 'antd';
import { API_URL, API_KEY, IMAGE_BASE_URL, IMAGE_SIZE, POSTER_SIZE } from '../../Config'
import MainImage from './Sections/MainImage'
import GridCard from '../../commons/GridCards'
import SearchMenu from '../LandingPage/Sections/SearchMenu'
const { Title } = Typography;
function LandingPage(props) {
const [searchTerm, setSearchTerm] = useState('');
console.log("searchIitialTerm = " + searchTerm);
const buttonRef = useRef(null);
const [Movies, setMovies] = useState([])
const [MainMovieImage, setMainMovieImage] = useState(null)
const [Loading, setLoading] = useState(true)
const [CurrentPage, setCurrentPage] = useState(0)
console.log(props.SearchMenu);
console.log("searchTermLanding = " + searchTerm);
var path;
var loadpath;
onchange = (searchTerm) => {
if (searchTerm != '') {
path = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=1`;
loadpath = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=${CurrentPage + 1}`;
}
else if (searchTerm == '') {
path = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
loadpath = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${CurrentPage + 1}`;
}
}
useEffect(() => {
const endpoint = path;
fetchMovies(endpoint)
}, [])
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, [])
const fetchMovies = (endpoint) => {
fetch(endpoint)
.then(result => result.json())
.then(result => {
// console.log(result)
// console.log('Movies',...Movies)
// console.log('result',...result.results)
setMovies([...Movies, ...result.results])
setMainMovieImage(MainMovieImage || result.results[0])
setCurrentPage(result.page)
}, setLoading(false))
.catch(error => console.error('Error:', error)
)
}
const loadMoreItems = () => {
let endpoint = '';
setLoading(true)
console.log('CurrentPage', CurrentPage)
endpoint = loadpath;
fetchMovies(endpoint);
}
const handleScroll = () => {
const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight - 1) {
// loadMoreItems()
console.log('clicked')
buttonRef.current.click();
}
}
return (
<div>
<div className="menu__container menu_search">
<SearchMenu mode="horizontal" onChange={value => setSearchTerm(value)} />
</div>
<div style={{ width: '100%', margin: '0' }}>
{MainMovieImage &&
<MainImage
image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${MainMovieImage.backdrop_path}`}
title={MainMovieImage.original_title}
text={MainMovieImage.overview}
/>
}
<div style={{ width: '85%', margin: '1rem auto' }}>
<Title level={2} > Latest movies </Title>
<hr />
<Row gutter={[16, 16]}>
{Movies && Movies.map((movie, index) => (
<React.Fragment key={index}>
<GridCard
image={movie.poster_path ?
`${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
: null}
movieId={movie.id}
movieName={movie.original_title}
/>
</React.Fragment>
))}
</Row>
{Loading &&
<div>Loading...</div>}
<br />
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button ref={buttonRef} className="loadMore" onClick={loadMoreItems}>Load More</button>
</div>
</div>
</div>
</div>
)
}
export default LandingPage
**
SearchMenu code:
**
import React, { useState } from 'react';
import { Route, Switch } from "react-router-dom";
import { Menu } from 'antd';
import { Input } from 'antd';
//import LandingPage from '../../LandingPage/LandingPage';
import '../../NavBar/Sections/Navbar.css';
const SearchMenu = (props) => {
console.log("props = " + props);
const [searchTerm, setSearchTerm] = useState("");
const { Search } = Input;
const onSearch = value => console.log(value);
function searchChangeHandler(e) {
e.preventDefault();
console.log(e.target.value);
setSearchTerm(e.target.value);
props.onChange(e.target.value);
}
console.log("searchTerm = " + searchTerm);
//console.log(props.onChange);
return (
<div className="searchMenu">
<Menu mode={props.mode} />
<Search
placeholder="Search"
allowClear onSearch={onSearch}
style={{ width: 400 }}
onChange={(e) => searchChangeHandler(e)}
/>
{/*
console.log("Search Term = " + searchTerm);
<LandingPage search={searchTerm}/>
*/}
</div>
)
}
export default SearchMenu;
The searchTerm state in LandingPage changes, but this doesn't trigger any updates to the API data. You defined an onchange function for the search term, but you haven't called it anywhere.
You can redo the search on every keystroke, or you can respond to clicks of a search button and onPressEnter in your search input. I'm going to redo the search on every change. So we've already got searchTerm updating -- we just need to use it!
I think it makes sense to set currentPage before loading data rather than after, but that's just my opinion. That way the effect can respond to changes in the page and the query.
Try this:
function LandingPage() {
const [searchTerm, setSearchTerm] = useState("");
const [movies, setMovies] = useState([]);
const [mainMovieImage, setMainMovieImage] = useState(null);
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
// do you need this? could just call loadMoreItems() instead of click()
const buttonRef = useRef(null);
const loadMoreItems = () => {
// just set the page, the effect will respond to it
setCurrentPage((page) => page + 1);
};
const onChangeSearch = (value) => {
// reset page to 1 when changing search
setSearchTerm(value);
setCurrentPage(1);
};
// run effect to load movies when the page or the searchTerm changes
useEffect(() => {
const endpoint =
searchTerm === ""
? `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${currentPage}`
: `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${encodeURIComponent(
searchTerm
)}&page=${currentPage}`;
// could use async/await but promise/then is fine too
setLoading(true);
fetch(endpoint)
.then((response) => response.json())
.then((json) => {
// replace state on page 1 of a new search
// otherwise append to exisiting
setMovies((previous) =>
currentPage === 1 ? json.results : [...previous, ...json.results]
);
// only replace if not already set
// when should we reset this?
setMainMovieImage((previous) => previous || json.results[0]);
})
.catch((error) => console.error("Error:", error))
.finally(() => setLoading(false));
}, [searchTerm, currentPage]);
const handleScroll = () => {
const windowHeight =
"innerHeight" in window
? window.innerHeight
: document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight - 1) {
// loadMoreItems()
console.log("clicked");
buttonRef.current?.click();
}
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
// cleanup function
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div>
<div className="menu__container menu_search">
<SearchMenu
mode="horizontal"
value={searchTerm}
onChange={onChangeSearch}
/>
</div>
<div style={{ width: "100%", margin: "0" }}>
{mainMovieImage && (
<MainImage
image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${mainMovieImage.backdrop_path}`}
title={mainMovieImage.original_title}
text={mainMovieImage.overview}
/>
)}
<div style={{ width: "85%", margin: "1rem auto" }}>
<Title level={2}> Latest movies </Title>
<hr />
<Row gutter={[16, 16]}>
{movies &&
movies.map((movie, index) => (
<React.Fragment key={index}>
<GridCard
image={
movie.poster_path
? `${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
: null
}
movieId={movie.id}
movieName={movie.original_title}
/>
</React.Fragment>
))}
</Row>
{loading && <div>Loading...</div>}
<br />
<div style={{ display: "flex", justifyContent: "center" }}>
<button
ref={buttonRef}
className="loadMore"
onClick={loadMoreItems}
disabled={loading} // disable button when fetching results
>
Load More
</button>
</div>
</div>
</div>
</div>
);
}
I would make SearchMenu into a controlled component that both reads and updates the searchTerm state from LandingPage instead of having its own state.
const SearchMenu = ({ mode, value, onChange }) => {
return (
<div className="searchMenu">
<Menu mode={mode} />
<Search
value={value}
placeholder="Search"
allowClear
style={{ width: 400 }}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
};

what is the main reason of not working the pressing remove button won't remove the item while pressing remove button won't remove the item?

i am trying to make a todo list in reacthook. my App.js:
import React,{useState} from 'react';
import AddTodo from './TodoFiles/AddTodo'
import TodoList from './TodoFiles/TodoList'
const defaultItems=[
{id:1,title:'Write React Todo Project',completed:true},
{id:2,title:'Upload it to github', completed:false}
]
const App=()=>{
const [items,setItems]=useState(defaultItems)
return(
<div style={{width:400}}>
<AddTodo items={items} setItems={setItems}/>
<br/>
<hr/>
<TodoList items={items}/>
<hr/>
</div>
)
}
export default App;
the addTodo.js is:
import React,{useState} from 'react'
const AddTodo=({items,setItems})=>{
const[title,setTitle]=useState('')
const handleTitle=(event)=>{
setTitle(event.target.value)
}
const handleAddTodo=()=>{
const NewItem={title}
setItems([NewItem,...items])
}
return(
<form onSubmit={e=>{e.preventDefault();handleAddTodo()}}>
<input type="text" placeholder="enter new task..." style={{width:350,height:15}}
value={title} onChange={handleTitle}/>
<input type="submit" style={{float:'right', marginTop:2}}/>
</form>
)
}
export default AddTodo
TodoList.js is:
import React, { useState } from "react";
const TodoItem = ({ title, completed, completeTodo, removeTodo, index }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(index)}>
Complete
</button>
<button style={{ float: "right" }} onClick={removeTodo}>
Remove
</button>
</div>
);
};
const TodoList = ({ items = [], index }) => {
const [, setItems] = useState("");
const completeTodo = index => {
console.log(index);
const newItem = [...items];
newItem[index].completed = true;
setItems(newItem);
};
const removeTodo = index => {
setItems(items.filter((p,index)=>p.index!==index))
};
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
CompeleteTodo has been resolved but when i press the remove button it is not working and nothing has been deleted. there is no error while executing npm start. developer tools showing a warning:
index.js:1 Warning: Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.
in input (at TodoList.js:6)
in div (at TodoList.js:5)
in TodoItem (at TodoList.js:30)
in TodoList (at App.js:18)
in div (at App.js:13)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
what more i can to to fix it?
You don't set index in parameter of your function, simply you can do this:
const TodoItem = ({ title, completed, completeTodo, removeTodo, index }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(index)}>
Remove
</button>
<button style={{ float: "right" }} onClick={removeTodo}>
Complete
</button>
</div>
);
};
const TodoList = ({ items = [], index }) => {
const [, setItems] = useState("");
const completeTodo = index => {
console.log(index);
const newItem = [...items];
newItem[index].completed = true;
setItems(newItem);
};
const removeTodo = index => {
const newItem = [...items];
newItem.splice(index, 1);
setItems(newItem);
};
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
and your remove and complete function is upside down :)
Check this
here is my suggestion > implement remove-complete functional with array items id and not index of elements.
please read this Lists and Keys
Here is Demo
App
import React, { useState } from "react";
import AddTodo from "./TodoFiles/AddTodo";
import TodoList from "./TodoFiles/TodoList";
const defaultItems = [
{ id: 1, title: "Write React Todo Project", completed: true },
{ id: 2, title: "Upload it to github", completed: false }
];
const App = () => {
const [items, setItems] = useState(defaultItems);
return (
<div style={{ width: 400 }}>
<AddTodo items={items} setItems={setItems} />
<br />
<hr />
<TodoList items={items} setItems={setItems} />
<hr />
</div>
);
};
export default App;
AddTodo
import React, { useState } from "react";
const AddTodo = ({ items, setItems }) => {
const [title, setTitle] = useState("");
const handleTitle = event => {
setTitle(event.target.value);
};
const handleAddTodo = () => {
const newItem = [
{
id: Math.max(...items.map(x => x.id), 0) + 1
completed: false,
title
},
...items
];
setItems(newItem);
};
return (
<form
onSubmit={e => {
e.preventDefault();
handleAddTodo();
}}
>
<input
type="text"
placeholder="enter new task..."
style={{ width: 350, height: 15 }}
value={title}
onChange={handleTitle}
/>
<input type="submit" style={{ float: "right", marginTop: 2 }} />
</form>
);
};
export default AddTodo;
TodoList
import React from "react";
import TodoItem from "./TodoItem";
const TodoList = ({ items, setItems }) => {
const completeTodo = id => {
setItems(
items.map(item => (item.id === id ? { ...item, completed: true } : item))
);
};
const removeTodo = id => {
setItems(items.filter(p => p.id !== id));
};
return items.map(p => (
<TodoItem
{...p}
key={p.id}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
TodoItem
import React from "react";
const TodoItem = ({ id, title, completed, completeTodo, removeTodo }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} onChange={() => {}} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(id)}>
Complete
</button>
<button style={{ float: "right" }} onClick={() => removeTodo(id)}>
Remove
</button>
</div>
);
};
export default TodoItem;

Categories

Resources