Passing props to modal passes every object - javascript

I'm not 100% sure what's going on here. I've got a display component that displays a bunch of cards, using a map based on my database - On the card is an edit button that pops a modal up, passing props over to the edit form.. Here's kinda how it looks:
import React, { useState } from 'react'
import { useQuery, useMutation } from '#apollo/client'
import { GET_ALL_PROJECTS, REMOVE_PROJECT } from '../helpers/queries'
import { makeStyles } from '#material-ui/core/styles'
import DeleteIcon from '#material-ui/icons/Delete'
import EditIcon from '#material-ui/icons/Edit'
import AddForm from './AddForm'
import EditForm from './EditForm'
import AlertMessage from '../Alerts/AlertMessage'
import { Grid, Typography, Card, CardActionArea, CardActions, CardContent, CardMedia, Button, Modal, Backdrop, Fade } from '#material-ui/core'
const useStyles = makeStyles((theme) => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
paper: {
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
const DisplayProjects = () => {
const styles = useStyles()
const [deleteItem] = useMutation(REMOVE_PROJECT)
const { loading, error, data } = useQuery(GET_ALL_PROJECTS)
const [status, setStatusBase] = useState('')
const [resultMessage, setResultMessage] = useState('')
const [addOpen, setAddOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const onDelete = (id, e) => {
e.preventDefault()
deleteItem({
variables: { id },
refetchQueries: [{ query: GET_ALL_PROJECTS }]
}).then(
res => handleSuccess(res),
err => handleError(err)
)
}
// Handles Result of the Delete Operation
const handleSuccess = (res) => {
console.log(res.data.deleteProject.proj_name)
// console.log('success!');
setResultMessage(res.data.deleteProject.proj_name)
setStatusBase({
msg: `Successfully Deleted ${resultMessage}`,
key: Math.random()
})
}
const handleError = (err) => {
console.log('error')
}
//Handles the Modal for Add Project
const handleAddOpen = () => {
setAddOpen(true);
};
const handleAddClose = () => {
setAddOpen(false);
};
//Handles the Modal for Edit Project
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
if (loading) return '...Loading'
if (error) return `Error: ${error.message}`
return (
<div>
<div style={{ marginTop: 20, padding: 30 }}>
<Grid container spacing={8} justify='center' alignItems='center'>
{data.projects.map(p => {
return (
<Grid item key={p._id}>
<Card >
<CardActionArea>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CardMedia
style={{ width: 400, height: 100, paddingTop: 10, }}
component='img'
alt='Project Image'
height='140'
image={require('../../images/html-css-javascript-lg.jpg')}
/>
</div>
<CardContent >
<Typography gutterBottom variant='h5' component="h2">
{p.proj_name}
</Typography>
<Typography component='p'>
{p.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button>
<DeleteIcon onClick={e => onDelete(p._id, e)} />
</Button>
<Button onClick={handleEditOpen}>
<Modal
open={editOpen}
onClose={handleEditClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={editOpen}>
<div className={styles.paper}>
<EditForm
id={p._id}
close={handleEditClose}
name={p.proj_name}
desc={p.description}
gh={p.gh_link}
live={p.live_link}
img={p.image_url}
/>
</div>
</Fade>
</Modal>
<EditIcon />
</Button>
</CardActions>
</Card>
{ status ? <AlertMessage key={status.key} message={status.msg} /> : null}
</Grid>
)
}
)}
</Grid>
<Button type='button' onClick={handleAddOpen}>Add Project</Button>
<Modal
open={addOpen}
onClose={handleAddClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={addOpen}>
<div className={styles.paper}>
<AddForm close={handleAddClose} />
</div>
</Fade>
</Modal>
</div>
</div >
)
}
export default DisplayProjects
And here's the form. I've destructured out the props into variables and placed them into a state object called details, so they can be overwritten and submitted to the database..
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import { useMutation, useQuery } from '#apollo/client'
import { EDIT_PROJECT, GET_ALL_PROJECTS, GET_PROJECT_BY_ID} from '../helpers/queries'
const AddForm = (props) => {
const params = useParams()
const id = params.toString()
// console.log(id);
const [editProjectItem] = useMutation(EDIT_PROJECT)
const {loading, data, error} = useQuery(GET_PROJECT_BY_ID, {
variables: {
id
},
})
const [details, setDetails] = useState({})
if (loading) return '...Loading';
if (error) return <p>ERROR: {error.message}</p>;
if (!data) return <p>Not found</p>;
setDetails(data.projectById)
console.log(data.projectById)
const submitForm = e => {
e.preventDefault()
try {
editProjectItem({
variables: { id, proj_name, description, gh_link, live_link, image_url},
refetchQueries: [{query: GET_ALL_PROJECTS}]
})
}
catch (err) {
console.log('You Goofed')
}
// setDetails({
// proj_name: '',
// description: '',
// gh_link: '',
// live_link: '',
// image_url: ''
// })
props.close()
}
const changeDetails = (e) => {
setDetails({
...details,
[e.target.name]: e.target.value
})
}
const {_id, proj_name, description, gh_link, live_link, image_url} = details
return (
<div key = {_id}>
<h2>Edit {proj_name}</h2>
<form onSubmit = {submitForm} >
<label>
Project Name:
<input
name = 'proj_name'
value = {proj_name}
onChange = {changeDetails}
/>
</label>
<label>Description</label>
<input
name = 'description'
value = {description}
onChange = {changeDetails}
/>
<label>GitHub Link</label>
<input
name = 'gh_link'
value = {gh_link}
onChange = {changeDetails}
/>
<label>Live Link</label>
<input
name = 'live_link'
value = {live_link}
onChange = {changeDetails}
/>
<label>Preview Image</label>
<input
name = 'image_url'
value = {image_url}
onChange = {changeDetails}
/>
<button type = 'submit'>Submit</button>
</form>
</div>
)
}
export default AddForm
The problem I'm running into, is that when I access the modal, the props are sent from literally EVERY Object, instead of the one, and displays the data for the last record instead of the one I want to edit You can see what happens here (I logged props.id in order to test) https://imgur.com/a/pcEKl89
What did I miss? (Disclaimer: I am still a student, and learning the craft.. be gentle on my code please)
EDIT: I just realized that I didn't indicate that this is the final form of the EditForm component. I haven't added the logic in to make the updates yet, I just wanted to get the data showing properly first.
EDIT2: I made some changes to how the ID is passed over, I was already using React-Router, so I went ahead and made a route to /edit/:id and then using useParams(), I got the ID that way. It seems to be working, however now I'm getting a Too many re-renders message. Updated the AddForm code above to reflect the changes..

I figured out the re-render issue.. it was as simple as dropping the setDetails function into a useEffect Hook:
useEffect(()=> {
if(data){
setDetails(data.projectById)
}
},[data])

Related

Why am i getting undefined in my console, when i try to subscribe and use my context using typescript and react

When trying to create a simple quiz app without the need to prop drilling I've stumbled upon an issue while trying to integrate context into the project. The issue is that when subscribing to my context as shown below and console. logging 'name', I get the value of undefined. What am I missing in order to get my name(stored in a state in my context) logged instead of getting undefined?
My context
import React, { createContext, Dispatch, SetStateAction, useContext, useState } from 'react';
export interface IUserContextType {
name: string;
test: string;
setName: Dispatch<SetStateAction<string>>;
setTest: Dispatch<SetStateAction<string>>;
}
type IQuizContextProvidorProps = {
children: React.ReactNode;
};
export const QuizContext = createContext({} as IUserContextType);
export const useQuizContext = () => useContext(QuizContext);
const QuizContexProvider = ({ children }: IQuizContextProvidorProps) => {
const [name, setName] = useState('Marvin');
const [test, setTest] = useState('This is a test');
const values = { name, test, setName, setTest };
return <QuizContext.Provider value={values}>{children}</QuizContext.Provider>;
};
export default QuizContexProvider;
My App
import { useState } from 'react';
import './App.css';
import quizApi from './utils/quiz.json';
import { IQuiz, IQuizAnswers } from './model/IQuiz';
import { Button, LinearProgress, Paper, styled, Typography } from '#mui/material';
import { Box } from '#mui/system';
import QuizContexProvider, { useQuizContext } from './utils/QuizContex';
const QuizContainer = styled(Box)(({ theme }) => ({
'.correct': {
backgroundColor: 'darkseagreen',
},
'.linearProgress': {
height: '1rem',
},
}));
function App() {
const { name, test } = useQuizContext();
console.log('name', name);
function shuffle(array: Array<any>) {
return array.sort(() => Math.random() - 0.5);
}
const quiz: Array<IQuiz> = shuffle(quizApi);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [progress, setProgress] = useState(0);
const [viewQuiz, setViewQuiz] = useState(true);
const [quizScore, setQuizScore] = useState(0);
const inkrementWith = 100 / quiz.length;
const handleProgress = () => {
setProgress(progress + inkrementWith);
};
const handleAnswer = (answers: IQuizAnswers) => {
const nextQuestion = currentQuestionIndex + 1;
handleProgress();
if (nextQuestion < quiz.length) {
setCurrentQuestionIndex(nextQuestion);
} else {
setViewQuiz(false);
}
if (answers.isTrue === true) {
setQuizScore(quizScore + 1);
}
};
const handleReset = () => {
setCurrentQuestionIndex(0);
setProgress(0);
setQuizScore(0);
setViewQuiz(true);
};
return (
<QuizContexProvider>
<QuizContainer className='App'>
<Box component='header' className='App-header'>
{viewQuiz ? (
<>
<Box sx={{ width: '50%' }}>
<LinearProgress className='linearProgress' variant='determinate' color='success' value={progress} />
</Box>
{quiz.map(
(question, index) =>
index === currentQuestionIndex && (
<Box key={index}>
<Box>{question.questionLabel}</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', margin: '1rem' }}>
{shuffle(question.answerOptions).map((answers, index) => (
<Paper
key={index}
onClick={() => {
return handleAnswer(answers);
}}
component='button'
>
{answers.answerLabel}
</Paper>
))}
</Box>
</Box>
)
)}
</>
) : (
<Paper>
<Typography component='h1' variant='h3'>
Quiz results
</Typography>
<Typography component='h2' variant='subtitle1'>
Quiz results
</Typography>
<Typography component='h1' variant='h1' sx={{ fontWeight: 700 }}>
{quizScore} / {quiz.length}
</Typography>
<Button variant='contained' onClick={handleReset} sx={{ margin: '1rem 0rem' }}>
Reset quiz
</Button>
</Paper>
)}
</Box>
</QuizContainer>
</QuizContexProvider>
);
}
export default App;
Any component that wish to use context value, should be wrapped inside the provider. Your <App /> component is using context value, so it should be:
<QuizContexProvider>
<App />
</QuizContexProvider>
You can put the provider in Index.ts file.

push values in to new array only when material ui checkbox is checked

I am new to react and I am making a simple todo app using react js and material ui. I have separate components to take in user input (TodoInput.js) which sends props to a component that renders individual todo tasks and displays a checkbox (TodoCards.js). What I want to do is display the total number of completed tasks onto the page which is updated when the user completes a todo by checking a checkbox. To achieve this, I have an array that stores all the user's completed tasks. At the moment whenever a checkbox is checked, all tasks are added to this array. I ran into a problem where I am unsure of how to only push values into this new array when the checkbox of that specific task is checked. Any guidance or explanations towards the right direction is greatly appreciated.
TodoInput.js
import React, { useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import { TextField, Button } from '#material-ui/core';
import { TodoCards } from '../UI/TodoCards';
import { Progress } from '../UI/Progress';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
width: '25ch',
textAlign: 'center'
},
},
}));
export default function TodoInput() {
const classes = useStyles();
const [userInput, setUserInput] = useState({
id: '',
task: ''
});
const [todos, setTodos] = useState([])
//state for error
const [error, setError] = useState({
errorMessage: '',
error: false
})
//add the user todo with the button
const submitUserInput = (e) => {
e.preventDefault();
//add the user input to array
//task is undefined
if (userInput.task === "") {
//render visual warning for text input
setError({ errorMessage: 'Cannot be blank', error: true })
console.log('null')
} else {
setTodos([...todos, userInput])
console.log(todos)
setError({ errorMessage: '', error: false })
}
console.log(loadedTodos)
}
//set the todo card to the user input
const handleUserInput = function (e) {
//make a new todo object
setUserInput({
...userInput,
id: Math.random() * 100,
task: e.target.value
})
//setUserInput(e.target.value)
//console.log(userInput)
}
const loadedTodos = [];
for (const key in todos) {
loadedTodos.push({
id: Math.random() * 100,
taskName: todos[key].task
})
}
return (
<div>
<Progress taskCount={loadedTodos.length} />
<form className={classes.root} noValidate autoComplete="off" onSubmit={submitUserInput}>
{error.error ? <TextField id="outlined-error-helper-text" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} error={error.error} helperText={error.errorMessage} />
: <TextField id="outlined-basic" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} />}
<Button variant="contained" color="primary" type="submit">Submit</Button>
{userInput && <TodoCards taskValue={todos} />}
</form>
</div>
);
}
TodoCards.js
import React, { useState } from 'react'
import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '#material-ui/core';
import { CompletedTasks } from './CompletedTasks';
export const TodoCards = ({ taskValue }) => {
const [checked, setChecked] = useState(false);
//if checked, add the task value to the completed task array
const completedTasks = [];
const handleChecked = (e) => {
setChecked(e.target.checked)
for (const key in taskValue) {
completedTasks.push(taskValue[key])
}
console.log(completedTasks.length)
}
return (
< div >
<CompletedTasks completed={completedTasks.length} />
<Card>
{taskValue.map((individual, i) => {
return (
<CardContent key={i}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[i]}
onClick={handleChecked}
/>
}
label={individual.task} />
</Typography>
</CardContent>
)
})}
</Card>
</div >
)
}
CompletedTasks.js (displays the total number of completed tasks)
import React from 'react'
import InsertEmoticonOutlinedIcon from '#material-ui/icons/InsertEmoticonOutlined';
import { Typography } from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
padding: theme.spacing(2),
marginTop: '20px',
textAlign: 'center',
color: theme.palette.text.secondary,
},
}));
export const CompletedTasks = ({ completed }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<InsertEmoticonOutlinedIcon fontSize="large" />
<Typography variant="h6">
Completed tasks:{completed}
</Typography>
</div>
)
}
One issue I see here is that you start with a boolean type checked state in TodoCards, and only ever store a single boolean value of the last checkbox interacted with. There's no way to get a count or to track what's previously been checked.
Use an object to hold the completed "done" checked values, then count the number of values that are checked (i.e. true) after each state update and rerender. Use the task's id as the key in the checked state.
export const TodoCards = ({ taskValue = [] }) => {
const [checked, setChecked] = useState({});
const handleChecked = id => e => {
const { checked } = e.target;
setChecked((values) => ({
...values,
[id]: checked
}));
};
return (
<div>
<CompletedTasks
completed={Object.values(checked).filter(Boolean).length}
/>
<Card>
{taskValue.map(({ id, task }) => {
return (
<CardContent key={id}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[id]}
onClick={handleChecked(id)}
/>
}
label={task}
/>
</Typography>
</CardContent>
)
})}
</Card>
</div >
)
}
To do this, you first need to only push the checked key to your array:
First send your key to the eventHandler:
{taskValue.map((individual, i) => {
return (
<CardContent key={i}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[i]}
onClick={() => handleChecked(individual)}
/>
}
label={individual.task} />
</Typography>
</CardContent>
)
})}
Then in your Handler, push it into the array:
const handleChecked = (key) => {
//setChecked(e.target.checked)
completedTasks.push(key)
console.log(completedTasks.length)
}
BUT
Because you are not modifying any state so the changes won't be updated to the UI, you need to use a state to store your completedTasks.
const [completedTasks, setCompletedTasks] = useState([]);
const handleChecked = (key) => {
setCompletedTasks([...completedTasks, key])
}
Please note that this is only a guide so you can get to the right way, not a complete working example
In the hopes that someone else may find this useful, I was able to come up with a solution thanks to the suggestions given. Below is the updated code for the TodoCards.js component:
import React, { useState } from 'react'
import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '#material-ui/core';
import { CompletedTasks } from './CompletedTasks';
export const TodoCards = ({ taskValue }) => {
const [checked, setChecked] = useState(false);
//if checked, add the task value to the completed task array
const [completedTasks, setCompletedTasks] = useState([]);
const handleChecked = key => {
setCompletedTasks([...completedTasks, key])
completedTasks.push(key)
console.log(completedTasks.length)
setChecked(true)
};
if (taskValue.length === completedTasks.length) {
console.log('all tasks complete')
}
return (
<div>
<CompletedTasks completed={completedTasks.length} />
<Card>
{taskValue.map((individual, i) => {
return (
<CardContent key={i}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[i]}
onClick={() => handleChecked(individual)}
/>
}
label={individual.task}
/>
</Typography>
</CardContent>
)
})}
</Card>
</div >
)
}
Only the checked todo items are pushed into the new array (completedTasks) and this is updated using useState.

TypeError: Cannot read property 'data' of undefined for shopping cart functionality

I keep getting this error: TypeError: Cannot read property 'data' of undefined, when there is no data being passed to my shopping cart page. How can I fix this error? Ideally, I would just like the page to display: "This cart is empty". I tried adding a conditional statement above the UserCardBlock, but it did not change anything. Thank you
import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import {
removeCartItem,
onSuccessBuy
} from '../../../_actions/user_actions';
import UserCardBlock from './Sections/UserCardBlock';
import { Result, Empty, Button } from 'antd';
import Paypal from '../../utils/Paypal';
function CartPage(props) {
const dispatch = useDispatch();
console.log(props)
const [Total, setTotal] = useState(props.location.state.data.price)
const [ShowTotal, setShowTotal] = useState(true)
const [ShowSuccess, setShowSuccess] = useState(false)
const removeFromCart = (productId) => {
dispatch(removeCartItem(productId))
}
const transactionSuccess = (data) => {
dispatch(onSuccessBuy({
cartDetail: props.user.cartDetail,
paymentData: data
}))
.then(response => {
setShowSuccess(true)
setShowTotal(false)
}
)
}
const transactionError = () => {
console.log('Paypal error')
}
const transactionCanceled = () => {
console.log('Transaction canceled')
}
const propductList = (data) =>{
console.log(data)
setTotal(data)
}
return (
<div style={{ width: '85%', margin: '3rem auto' }}>
<h1>My Cart</h1>
<div>
<UserCardBlock
productData={props.location.state.data}
removeItem={removeFromCart}
productList={data => propductList(data)}
/>
{ShowTotal ? (
<div style={{ marginTop: "3rem" }}>
<h2>Total amount: ${Total * 15} </h2>
</div>
) : ShowSuccess ? (
<Result status="success" title="Successfully Purchased Items" />
) : (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
}}
>
<br />
<Empty description={false} />
<p>No Items In The Cart</p>
</div>
)}
</div>
{/* Paypal Button */}
{ShowTotal &&
<Paypal
toPay={Total}
onSuccess={transactionSuccess}
transactionError={transactionError}
transactionCanceled={transactionCanceled}
/>
}
</div>
)
}
export default CartPage
Seems like your component is dependent on location state.
const [Total, setTotal] = useState(props.location.state.data.price)
and
<UserCardBlock
productData={props.location.state.data}
Try using optional chaining and nullish coalescing
const [Total, setTotal] = useState(props.location?.state?.data?.price ?? 0)
<UserCardBlock
productData={props.location.state?.data ?? []}
It seems like you are using redux so i will suggest you to use redux store instead of location state.

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;

Setting the value of DatePicker (from antd) in react-hook-form

I'm trying to figure out how to use the DatePicker from antd with react-hook-form.
Currently, my attempt is:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useForm from "react-hook-form";
import { withRouter } from "react-router-dom";
import { useStateMachine } from "little-state-machine";
import updateAction from "./updateAction";
import { Input as InputField, Form, Button, DatePicker, Divider, Layout, Typography, Skeleton, Switch, Card, Icon, Avatar } from 'antd';
import Select from "react-select";
const { Content } = Layout
const { Text, Paragraph } = Typography;
const { Meta } = Card;
const { MonthPicker, RangePicker, WeekPicker } = DatePicker;
const Team = props => {
const { register, handleSubmit, setValue, errors } = useForm();
const [ dueDate, setDate ] = useState(new Date());
const [indexes, setIndexes] = React.useState([]);
const [counter, setCounter] = React.useState(0);
const { action } = useStateMachine(updateAction);
const onSubit = data => {
action(data);
props.history.push("./ProposalBudget");
};
// const handleChange = dueDate => setDate(date);
const handleChange = (e) => {
setValue("dueDate", e.target.value);
}
const onSubmit = data => {
console.log(data);
};
const addMilestone = () => {
setIndexes(prevIndexes => [...prevIndexes, counter]);
setCounter(prevCounter => prevCounter + 1);
};
const removeMilestone = index => () => {
setIndexes(prevIndexes => [...prevIndexes.filter(item => item !== index)]);
};
const clearMilestones = () => {
setIndexes([]);
};
useEffect(() => {
register({ name: dueDate }); // custom register antd input
}, [register]);
Note: i have also tried name: {${fieldName}.dueDate - that doesn't work either.
return (
<div>
<HeaderBranding />
<Content
style={{
background: '#fff',
padding: 24,
margin: "auto",
minHeight: 280,
width: '70%'
}}
>
<form onSubmit={handleSubmit(onSubit)}>
{indexes.map(index => {
const fieldName = `milestones[${index}]`;
return (
<fieldset name={fieldName} key={fieldName}>
<label>
Title:
<input
type="text"
name={`${fieldName}.title`}
ref={register}
/>
</label>
<label>
Description:
<textarea
rows={12}
name={`${fieldName}.description`}
ref={register}
/>
</label>
<label>When do you expect to complete this milestone? <br />
<DatePicker
selected={ dueDate }
// ref={register}
InputField name={`${fieldName}.dueDate`}
onChange={handleChange(index)}
//onChange={ handleChange }
>
<input
type="date"
name={`${fieldName}.dueDate`}
inputRef={register}
/>
</DatePicker>
</label>
<Button type="danger" style={{ marginBottom: '20px', float: 'right'}} onClick={removeMilestone(index)}>
Remove this Milestone
</Button>
</fieldset>
);
})}
<Button type="primary" style={{ marginBottom: '20px'}} onClick={addMilestone}>
Add a Milestone
</Button>
<br />
<Button type="button" style={{ marginBottom: '20px'}} onClick={clearMilestones}>
Clear Milestones
</Button>
<input type="submit" value="next - budget" />
</form>
</Content>
</div>
);
};
export default withRouter(Team);
This generates an error that says: TypeError: Cannot read property 'value' of undefined
setValue is defined in handleChange.
I'm not clear on what steps are outstanding to get this datepicker functioning. Do I need a separate select function?
Has anyone figured out how to plug this datepicker in?
I have also tried:
const handleChange = (e) => {
setValue("dueDate", e.target.Date);
}
and I have tried:
const handleChange = (e) => {
setValue("dueDate", e.target.date);
}
but each of these generations the same error
I have built a wrapper component to work with external controlled component easier:
https://github.com/react-hook-form/react-hook-form-input
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
function App() {
const { handleSubmit, register, setValue, reset } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: true }}
name="reactSelect"
register={register}
setValue={setValue}
/>
<button
type="button"
onClick={() => {
reset({
reactSelect: '',
});
}}
>
Reset Form
</button>
<button>submit</button>
</form>
);
}
try this out, let me know if it makes your life easier with AntD.
/* eslint-disable react/prop-types */
import React, { useState } from 'react';
import { DatePicker } from 'antd';
import { Controller } from 'react-hook-form';
import color from '../../assets/theme/color';
import DatePickerContainer from './DatePickerContainer';
function DatePickerAntd(props) {
const { control, rules, required, title, ...childProps } = props;
const { name } = childProps;
const [focus, setFocus] = useState(false);
const style = {
backgroundColor: color.white,
borderColor: color.primary,
borderRadius: 5,
marginBottom: '1vh',
marginTop: '1vh',
};
let styleError;
if (!focus && props.error) {
styleError = { borderColor: color.red };
}
return (
<div>
<Controller
as={
<DatePicker
style={{ ...style, ...styleError }}
size="large"
format="DD-MM-YYYY"
placeholder={props.placeholder || ''}
onBlur={() => {
setFocus(false);
}}
onFocus={() => {
setFocus(true);
}}
name={name}
/>
}
name={name}
control={control}
rules={rules}
onChange={([selected]) => ({ value: selected })}
/>
</div>
);
}
export default DatePickerAntd;
my container parent use react-hooks-form
const { handleSubmit, control, errors, reset, getValues } = useForm({
mode: 'onChange',
validationSchema: schema,
});
<DatePickerAntd
name="deadline"
title={messages.deadline}
error={errors.deadline}
control={control}
required={isFieldRequired(schema, 'deadline')}
/>
like that, its working for me ;-)
Try this:
<DatePicker
selected={ dueDate }
// ref={register}
InputField name={`${fieldName}.dueDate`}
onChange={()=>handleChange(index)}
//onChange={ handleChange }
>
<input
type="date"
name={`${fieldName}.dueDate`}
inputRef={register}
/>
It looks like if you are using onChange={handleChange(index)} it does not pass a function instead you are passing an execution result of that function.
And if you are trying to access event inside handleChange, you should manually pass if from binding scope otherwise, it will be undefined.
onChange={()=>handleChange(index, event)}

Categories

Resources