I am having two components, App and a panel. On button clcik, I add panel to the screen and all the actions corresponding actions inside of the panel is handled in the Panel component ( Actions are expand, collapse and close). Can I somehow execute the same actions inside of the app component using useImperativeHandle hook using ref's. Also can I execute onClose method inside of the Panel component, here i am actually as a callback.
https://codesandbox.io/s/basic-demo-card-6ywop7?file=/src/Panel.jsx:0-985
Can someone help me here
App
import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
import Panel from "./Panel";
import "./styles.css";
function App() {
const [card, setCard] = useState({
cardId: "",
cardBody: null
});
const ref = useRef();
const handleClick = (cardId, cardBody) => {
setCard({ cardId, cardBody });
};
const { cardId, cardBody } = card;
return (
<>
<div className="main">
<button onClick={() => ref?.current?.expandBtn()}>Open from out</button>
<button onClick={() => handleClick("Panel 1", <h1>h1</h1>)}>
Add Panel 1
</button>
<button onClick={() => handleClick("Panel 2", <div>div</div>)}>
Add Panel 2
</button>
</div>
{cardBody && (
<div className="cards-container">
<Panel
key={cardId}
cardId={cardId}
cardBody={cardBody}
onClose={() =>
setCard({
cardId: "",
cardBody: null
})
}
/>
</div>
)}
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Panel
import React, { useImperativeHandle, useState, useRef } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faSquareMinus,
faRectangleXmark
} from "#fortawesome/free-solid-svg-icons";
export default function Panel(props) {
const [isMinimized, setIsMinimized] = useState(false);
const { cardId, cardBody, onClose, ref } = props;
const expandRef = useRef();
useImperativeHandle(ref, () => {
return {
expandBtn: () => expandRef.current.onMaximize()
};
});
const onMaximize = () => {
setIsMinimized(!isMinimized);
};
return (
<>
<div className={isMinimized ? "card-min" : "card"}>
<div className="card-actions">
<span onClick={onMaximize}>{cardId}</span>
{!isMinimized && (
<FontAwesomeIcon
icon={faSquareMinus}
onClick={() => {
setIsMinimized(true);
}}
/>
)}
<FontAwesomeIcon icon={faRectangleXmark} onClick={onClose} />
</div>
<div className="card-body">{cardBody}</div>
</div>
</>
);
}
Yes, you can the child's function from the Parent using useImperativeHandle. What your implementation is missing is forwardRef. passing ref as props won't work. What you have to do is forward the ref from child to parent.
const Panel = React.forwardRef(function (props, ref) {
const [isMinimized, setIsMinimized] = useState(false);
const { cardId, cardBody, onClose } = props;
const onMaximize = () => {
setIsMinimized(!isMinimized);
};
useImperativeHandle(ref, () => {
return {
expandBtn: onMaximize
};
});
return (
<>
<div className={isMinimized ? "card-min" : "card"}>
<div className="card-actions">
<span onClick={onMaximize}>{cardId}</span>
{!isMinimized && (
<FontAwesomeIcon
icon={faSquareMinus}
onClick={() => {
setIsMinimized(true);
}}
/>
)}
<FontAwesomeIcon icon={faRectangleXmark} onClick={onClose} />
</div>
<div className="card-body">{cardBody}</div>
</div>
</>
);
})
export default Panel;
Pass the ref from the App
function App() {
const [card, setCard] = useState({
cardId: "",
cardBody: null
});
const ref = useRef();
const handleClick = (cardId, cardBody) => {
setCard({ cardId, cardBody });
};
const { cardId, cardBody } = card;
return (
<>
<div className="main">
<button onClick={() => ref?.current?.expandBtn()}>Open from
out</button>
<button onClick={() => handleClick("Panel 1", <h1>h1</h1>)}>
Add Panel 1
</button>
<button onClick={() => handleClick("Panel 2", <div>div</div>)}>
Add Panel 2
</button>
</div>
{cardBody && (
<div className="cards-container">
<Panel
ref={ref}
key={cardId}
cardId={cardId}
cardBody={cardBody}
onClose={() =>
setCard({
cardId: "",
cardBody: null
})
}
/>
</div>
)}
</>
);
}
This might help https://blogsbyarjun.hashnode.dev/how-to-update-childs-state-from-parent-in-react-1
Related
I am having two components, App and a panel. On button clcik, I add panel to the screen and all the actions corresponding actions inside of the panel is handled in the Panel component ( Actions are expand, collapse and close). Can I execute onClose method inside of the Panel component instead in App component. I am having a check of if cardBody is present in the parent component. For removing that card im setting its body to null inside App component. How can I do the same in Panel component.
https://codesandbox.io/s/basic-demo-card-6ywop7?file=/src/Panel.jsx:0-1022
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Panel from "./Panel";
import "./styles.css";
function App() {
const [card, setCard] = useState({
cardId: "",
cardBody: null
});
const handleClick = (cardId, cardBody) => {
setCard({ cardId, cardBody });
};
const { cardId, cardBody } = card;
return (
<>
<div className="main">
<button onClick={() => handleClick("Panel 1", <h1>h1</h1>)}>
Add Panel 1
</button>
<button onClick={() => handleClick("Panel 2", <div>div</div>)}>
Add Panel 2
</button>
</div>
{cardBody && (
<div className="cards-container">
<Panel
key={cardId}
cardId={cardId}
cardBody={cardBody}
onClose={() =>
setCard({
cardId: "",
cardBody: null
})
}
/>
</div>
)}
</>
);
}
import React, { useState, useCallback } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faSquareMinus,
faRectangleXmark
} from "#fortawesome/free-solid-svg-icons";
function Panel(props) {
const [isMinimized, setIsMinimized] = useState(false);
const { cardId, cardBody, onClose } = props;
const onMaximize = useCallback(() => {
setIsMinimized(!isMinimized);
}, [isMinimized]);
return (
<>
<div className={isMinimized ? "card-min" : "card"}>
<div className="card-actions">
<span onClick={onMaximize}>{cardId}</span>
{!isMinimized && (
<FontAwesomeIcon
icon={faSquareMinus}
onClick={() => {
setIsMinimized(true);
}}
/>
)}
<FontAwesomeIcon icon={faRectangleXmark} onClick={onClose} />
</div>
<div className="card-body">{cardBody}</div>
</div>
</>
);
}
export default Panel;
One workaround is to control the show/hide by display: 'none'/'block' inside the Panel component.
Panel component
import React, { useImperativeHandle, useState, useCallback } from
"react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faSquareMinus,
faRectangleXmark
} from "#fortawesome/free-solid-svg-icons";
function Panel(props) {
const [isMinimized, setIsMinimized] = useState(false);
const { cardId, cardBody, setCard } = props;
const onMaximize = useCallback(() => {
setIsMinimized(!isMinimized);
}, [isMinimized]);
return (
<>
<div
style={{ display: cardBody ? "block" : "none" }}
className={isMinimized ? "card-min" : "card"}
>
<div className="card-actions">
<span onClick={onMaximize}>{cardId}</span>
{!isMinimized && (
<FontAwesomeIcon
icon={faSquareMinus}
onClick={() => {
setIsMinimized(true);
}}
/>
)}
<FontAwesomeIcon
icon={faRectangleXmark}
onClick={() =>
setCard({
cardId: "",
cardBody: null
})
}
/>
</div>
<div className="card-body">{cardBody}</div>
</div>
</>
);
}
export default Panel;
App.js
import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
import Panel from "./Panel";
import "./styles.css";
function App() {
const [card, setCard] = useState({
cardId: "",
cardBody: null
});
const handleClick = (cardId, cardBody) => {
setCard({ cardId, cardBody });
};
const { cardId, cardBody } = card;
return (
<>
<div className="main">
<button onClick={() => handleClick("Panel 1", <h1>h1</h1>)}>
Add Panel 1
</button>
<button onClick={() => handleClick("Panel 2", <div>div</div>)}>
Add Panel 2
</button>
</div>
<div className="cards-container">
<Panel
key={cardId}
cardId={cardId}
cardBody={cardBody}
setCard={setCard}
/>
</div>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This is working example
Also, you can completely conditionally show and hide the inner content of the panel but you need some modifications in the style by moving the "card-min" and "card" class to an inner div instead of the root element on the component.
I have a register page and Modal component. In register has a useState for visibility of the Modal. I'm passing it as a prop to Modal. When the modal is closed how to change the useState value in the register page.
Register page:
import React, { useState } from 'react'
import {
CCard,
CButton,
CCardBody,
CCardHeader,
CCol,
CForm,
CFormInput,
CFormLabel,
CSpinner,
CRow,
} from '#coreui/react'
import CIcon from '#coreui/icons-react'
import { cilSend } from '#coreui/icons'
import Alert from 'src/components/Alert'
import Modal from 'src/components/Modal'
const FormControl = () => {
const [disabled, setDisabled] = useState(false)
const [visible, setVisible] = useState(false)
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const handleAddMember = async () => {
try {
const data = { email, name }
const _data = await fetch('http://localhost:4000/api/v1/member/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + localStorage.getItem('token'),
},
body: JSON.stringify(data),
})
if (_data.status === 201) {
setVisible(true)
setDisabled(false)
} else if (_data.status === 422) {
setDisabled(false)
} else {
setDisabled(false)
throw new Error()
}
} catch (err) {
setDisabled(false)
}
}
return (
<CRow>
<Modal visible={visible} message="Member added to your community successfully!" />
<CCol xs={6}>
<CCard className="mb-4">
<CCardHeader>
<strong>Add New Member</strong>
</CCardHeader>
<CCardBody>
<p className="text-medium-emphasis small">
Fill in the email address field and name field to add a new member to your community.
</p>
<CForm>
<div className="mb-3">
<CFormLabel>Email address:</CFormLabel>
<CFormInput
type="email"
placeholder="name#example.com"
onChange={(e) => {
setEmail(e.target.value)
}}
/>
</div>
<div className="mb-3">
<CFormLabel>Name:</CFormLabel>
<CFormInput
type="text"
placeholder="Perera's Home"
onChange={(e) => {
setName(e.target.value)
}}
/>
</div>
<div className="mb-3">
<CButton color="primary" disabled={disabled} onClick={() => handleAddMember()}>
{disabled ? (
<CSpinner component="span" className="me-2" size="sm" aria-hidden="true" />
) : (
<CIcon icon={cilSend} className="me-2" />
)}
Submit
</CButton>
</div>
</CForm>
</CCardBody>
</CCard>
</CCol>
</CRow>
)
}
export default FormControl
Modal component:
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { CButton, CModal, CModalBody, CModalFooter, CModalHeader, CModalTitle } from '#coreui/react'
const Modal = (props) => {
const [visible, setVisible] = useState(props.visible)
return (
<CModal alignment="center" visible={visible} onClose={() => setVisible(false)}>
<CModalHeader>
<CModalTitle>Success!</CModalTitle>
</CModalHeader>
<CModalBody>{props.message}</CModalBody>
<CModalFooter>
<CButton color="primary" onClick={() => setVisible(false)}>
Close
</CButton>
</CModalFooter>
</CModal>
)
}
Modal.propTypes = {
visible: PropTypes.bool,
message: PropTypes.string,
}
export default React.memo(Modal)
You should have just one visible state member, either in the parent component or in the child (Modal), rather than having it in both places.
If you put it in the parent, you can pass it to the child just like any other prop:
return <Modal visible={visible} setVisible={setVisible}>{/*...*/}</Modal>
Modal's code can then call props.setVisible with the appropriate flag.
If you only want Modal to be able to hide itself (not show itself), you might instead pass a wrapper function that calls setVisible(false):
const hide = useCallback(() => setVisible(false), [setVisible]);
// Optional, see below −−−−−−−−−−−−−^^^^^^^^^^
// ...
return <Modal visible={visible} hide={hide}>{/*...*/}</Modal>
...and then Modal's code calls hide() to hide the modal.
(Making setVisible a dependency in the useCallback call is optional; state setter functions are stable; they don't change during the lifetime of the component. Some linters aren't quite smart enough to realize that and may nag you if you don't include it, but most are smarter than that.)
Here's a highly simplified example:
const {useState} = React;
const Example = () => {
const [visible, setVisible] = useState(false);
return <div>
<input type="button" value="Open" disabled={visible} onClick={() => setVisible(true)} />
<Modal visible={visible} setVisible={setVisible} />
</div>;
};
const Modal = (props) => {
if (!props.visible) {
return null;
}
return <div className="modal">
<div>This is the modal</div>
<input type="button" value="Close" onClick={() => props.setVisible(false)} />
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.modal {
border: 1px solid grey;
padding: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Or with destructuring (I generally use destructuring with props, but it didn't look like you were):
const {useState} = React;
const Example = () => {
const [visible, setVisible] = useState(false);
return <div>
<input type="button" value="Open" disabled={visible} onClick={() => setVisible(true)} />
<Modal visible={visible} setVisible={setVisible} />
</div>;
};
const Modal = ({visible, setVisible}) => {
if (!visible) {
return null;
}
return <div className="modal">
<div>This is the modal</div>
<input type="button" value="Close" onClick={() => setVisible(false)} />
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.modal {
border: 1px solid grey;
padding: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
you can pass the setVisible as well in the modal component and then use the same setState on both component
<Modal visible={visible} setVisible={setVisible} message="Member added to your community successfully!" />
use this like
props.visible
props.setVisible
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>
)
}
I use Antd. I have Modal Window which consist Form. I want to focus in Input Field when user open the modal widnow. How i can do it in functional component? I try this but in not work for me:
const EditForm = ({visible, widget, onSave, onCancel}) => {
const nameInput = useRef();
useEffect(() => nameInput.current && nameInput.current.focus());
const [form] = Form.useForm();
return (
<div>
<Modal
visible={visible}
title='Edit'
okText='Save'
cancelText='Cancel'
onCancel={onCancel}
onOk={() => {
form
.validateFields()
.then(values => {
form.resetFields();
onSave(values);
})
.catch(info => {
console.log('Validate Failed:', info);
});
}}
>
<Form
{...formItemLayout}
layout={formLayout}
form={form}
>
<Form.Item />
<Form.Item
name='nameWidget'
label='Name'
>
<Input name='nameWidget' ref={nameInput} onChange={handleChangeName} placeholder='Введите новое название' />
</Form.Item>
</Form>
</Modal>
</div>
);
};
try this way.
good luck ;)
import React, {useState, useRef, useEffect} from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Modal, Button, Input, Form } from 'antd';
const App = () => {
const [visible, setVisible] = useState(false)
const myRef = useRef();
/*
* This is the main different
*/
useEffect(()=>{
if (myRef && myRef.current) {
const { input } = myRef.current
input.focus()
}
})
const showModal = () => {
setVisible(true)
};
const handleOk = e => {
setVisible(false)
};
const handleCancel = e => {
setVisible(false)
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with customized button props
</Button>
<Modal
title="Basic Modal"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ disabled: true }}
cancelButtonProps={{ disabled: true }}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<Form>
<Form.Item>
<Input ref={myRef} />
</Form.Item>
</Form>
</Modal>
</>
);
}
ReactDOM.render(<App />, document.getElementById('container'));
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;