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
Related
I have a form that uses accordion component.
When I print values using watch() then collapse accordion. the values get deleted from inputs when I open it again.
This behaviour is not happening when I don't use watch()
I would like to know why this is happening ? watch() should only listen to data as I know.
CodeSandbox
CreateTest.ts
import { QuestionCreatingForm } from "./QuestionForm";
import {
AccordionHeader,
AccordionItem,
AccordionPanel,
Accordion
} from "#fluentui/react-components";
import { Button, Form } from "#fluentui/react-northstar";
import { useFieldArray, useForm } from "react-hook-form";
export function CreateTest() {
const methods = useForm();
const { control, register, handleSubmit, watch } = methods;
const { fields, append } = useFieldArray({
control,
name: "questions"
});
const addQuestion = (event: any) => {
event.preventDefault();
append({ name: "" });
};
const onSubmit = (data: any) => {
alert(JSON.stringify(data));
};
return (
<div className="w-8/12 m-auto">
{JSON.stringify(watch())}
<Form onSubmit={handleSubmit(onSubmit)}>
{fields.map((field, index) => (
<Accordion key={field.id} collapsible>
<AccordionItem value="1">
<AccordionHeader>Accordion Header </AccordionHeader>
<AccordionPanel>
<QuestionCreatingForm
fieldId={field.id}
index={index}
{...{ control, register, watch }}
/>
</AccordionPanel>
</AccordionItem>
</Accordion>
))}
<Button
className="my-10"
content="Add question"
primary
fluid
onClick={addQuestion}
/>
<Button
className="my-10"
fluid
content="submit"
primary
type="submit"
/>
</Form>
{/* </FormProvider> */}
</div>
);
}
QuestionForm.ts
import {
Button,
Divider,
FormCheckbox,
FormInput,
TrashCanIcon
} from "#fluentui/react-northstar";
import { SyntheticEvent } from "react";
import {
Control,
FieldValues,
useFieldArray,
UseFormRegister,
UseFormWatch
} from "react-hook-form";
export function QuestionCreatingForm({
index,
fieldId,
control,
register,
watch
}: {
index: number;
fieldId: string;
control: Control<FieldValues, any>;
register: UseFormRegister<FieldValues>;
watch: UseFormWatch<FieldValues>;
}) {
const { fields, append, remove } = useFieldArray({
control,
name: `questions.${index}.responses`
});
const addResponse = (event: SyntheticEvent<HTMLElement, Event>) => {
event.preventDefault();
append({ name: "" });
};
const deleteResponse = (index: number) => {
remove(index);
};
return (
<>
<FormInput
label="Question"
required
fluid
key={index}
{...register(`questions.${index}.name` as const)}
/>
<div className="w-10/12 m-auto">
{fields.map((field, i) => (
<div className="flex w-full">
<FormCheckbox />
<div className="w-full" key={field.id}>
<FormInput
{...register(`questions.${index}.responses.${i}.name` as const)}
defaultValue=""
label={`reponses ${i + 1}`}
required
fluid
/>
</div>
<Button
text
styles={{ color: "red", placeSelf: "end" }}
icon={<TrashCanIcon />}
onClick={(e) => deleteResponse(i)}
iconOnly
/>
</div>
))}
<Button
content="Ajouter une réponse"
tinted
fluid
onClick={addResponse}
/>
</div>
<Divider />
</>
);
}
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
I am currently learning React by following a video by Clever Programmer
(https://www.youtube.com/watch?v=pUxrDcITyjg&list=PLvmRwCtZ6YKRBCjKGNEbmOd816fgvptZc&index=17&t=20s)
However, towards the end I am finding an error which is not openly reported in comments or threads.
I have localised it to the following file "Chat.js" which contains the below code.
My question is: What could be tripping the error "Each child in a list should have a unique "key" prop.".
Advice or solution would be great, but if solved, please provide reasoning as I will need to learn why it was not working!
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import "./Chat.css";
import { useStateValue } from "./StateProvider.js";
import { Avatar, IconButton } from "#material-ui/core";
import {
AttachFile,
InsertEmoticon,
Mic,
SearchOutlined,
} from "#material-ui/icons";
import MoreVert from "#material-ui/icons/MoreVert";
import db from "./firebase";
import firebase from 'firebase/compat/app';
function Chat() {
const [input, setInput] = useState("");
const [seed, setSeed] = useState("");
const { roomId } = useParams();
const [roomName, setRoomName] = useState("");
const [messages, setMessages] = useState([]);
const [{ user }, dispatch] = useStateValue();
useEffect(() => {
if (roomId) {
db.collection("rooms")
.doc(roomId)
.onSnapshot((snapshot) => setRoomName(snapshot.data().name));
db.collection('rooms')
.doc(roomId)
.collection("messages")
.orderBy('timestamp', 'asc')
.onSnapshot(snapshot =>
setMessages(snapshot.docs.map(doc => doc.data()))
);
}
}, [roomId]);
useEffect(() => {
setSeed(Math.floor(Math.random() * 5000));
}, [roomId]);
const sendMessage = (e) => {
e.preventDefault();
console.log("You typed: >>>", input);
db.collection('rooms').doc(roomId).collection('messages').add({
message: input,
user: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
})
setInput("");
};
return (
<div className="chat">
<div className="chat__header">
<Avatar src={`https://avatars.dicebear.com/api/human/${seed}.svg`} />
<div className="chat__headerInfo">
<h3>{roomName}</h3>
<p>Last seen...</p>
</div>
<div className="chat__headerRight">
<IconButton>
<SearchOutlined />
</IconButton>
<IconButton>
<AttachFile />
</IconButton>
<IconButton>
<MoreVert />
</IconButton>
</div>
</div>
{/* The div "chat__body" incorporates the entire message structure */}
<div className="chat__body">
{messages.map((message) => (
<p className={`chat__message ${message.name === user.displayName && "chat__receiver"}`}>
<span className="chat__name">{message.name}</span>
{message.message}
<span className="chat__timeStamp">
{new Date(message.timestamp?.toDate()).toUTCString()}
</span>
</p>
))}
</div>
<div className="chat__footer">
<InsertEmoticon />
<form>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message"
type="text"
/>
<button onClick={sendMessage} type="submit">
Send a message
</button>
</form>
<Mic />
</div>
</div>
);
}
export default Chat;
messages.map is returning a list, and each item in it must have a unique key value that identifies it.
Either from an id from the current item
messages.map((message) => (
<p key={message.id} className...
or the current index
messages.map((message, i) => (
<p key={i} className...
following is the AddCourse page
AddCourse.js
import React, { useEffect, useState } from 'react';
import { Button, Form, FormGroup, Label, Input, FormText, Container } from 'reactstrap';
import database from '../services/fire';
import { useSelector, useDispatch } from 'react-redux';
import uuid from 'react-uuid';
import '../App.css';
const AddCourse = () => {
const [courseId, setCourseId] = useState('');
const [courseTitle, setCourseTitle] = useState('');
const [courseDesc, setCourseDesc] = useState('');
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user.uid);
useEffect(() => {
document.title = "Add Courses"
}, [])
const addCourse = () => {
const payload = { id: uuid(), courseId:courseId, courseTitle: courseTitle, courseDesc: courseDesc }
const dbcoursesWrapper = database.ref().child(user).child('courses');
// const dbcoursesWrapper = database.ref(`users/${user}/courses`).push(courseId, courseTitle, setCourseDesc);
return dbcoursesWrapper.child(payload.id).update(payload).then(() => {
setCourseId('');
setCourseTitle('');
setCourseDesc('');
dispatch({ type: "ADD_COURSES", payload });
})
}
return (
<div>
<h1 className="text-center my-3">Fill Course Detail</h1>
<Form onSubmit={(e) => {
e.preventDefault(e.target.value);
addCourse();
}}>
<FormGroup>
<label for="UserId">Course Id</label>
<Input
type="text"
value={courseId}
onChange={(e) => setCourseId(e.target.value)}
placeholder="Enter your Id"
name="userId"
id="UserId"
/>
</FormGroup>
<FormGroup>
<label for="title">Course Title</label>
<Input
type="text"
value={courseTitle}
onChange={(e)=> setCourseTitle(e.target.value)}
placeholder="Enter Course Title"
name="title"
id="title"
/>
</FormGroup>
<label for="description">Course Description</label>
<Input
value={courseDesc}
onChange={(e) => setCourseDesc(e.target.value)}
type="textarea"
placeholder="Enter Course Description"
name="description"
id="description"
style={{ height: 150 }}
/>
<Container className="text-center">
<Button color="success" type='submit'>Add Course</Button>
<Button color="warning ml-3">clear</Button>
</Container>
</Form>
</div>
);
};
export default AddCourse;
courses.js here is the update button when i click on it i want it to open the AddCourse page with the same values of the course i want to update not getting any clue how can i do this
import React from 'react';
import {
Card, CardText, CardBody,
CardTitle, Button, Container
} from 'reactstrap';
import database from '../services/fire';
import { useSelector, useDispatch } from 'react-redux';
import { fetchCourse } from '../actions/courses';
import AddCourse from './AddCourse';
const Course = ({course}) => {
const user = useSelector(state => state.auth.user.uid);
const dispatch = useDispatch();
const removeCourse = (id) => {
console.log(id);
const dbtasksWrapper = database.ref().child(user).child('courses');
dbtasksWrapper.child(id).remove().then(() => {
dispatch({ type: 'DELETE_COURSE', id: id })
dispatch(fetchCourse(user));
})
}
return (
<div>
<Card>
<CardBody className="text-center ">
<CardText className="text-center"><h2>CourseID: {course.courseId}</h2></CardText>
<CardTitle className="font-weight-bold text-center"><h1>{course.courseTitle}</h1></CardTitle>
<CardText className="text-center">{course.courseDesc}.</CardText>
<Container className="text-center">
{/* here is the update button and when onclick its goes to add course page with the course vale need to update** */}
<Button color="warning"onClick={}>Update</Button>
<Button color="danger ml-4" onClick={()=>removeCourse(course.id)}>Delete</Button>
</Container>
</CardBody>
</Card>
</div>
);
};
export default Course;
Sorry, not getting your question properly. You are trying to add a course using AddCourse.js component on submitting the form, then you want to display the course ID, Title and Description. In order to do this, you need the following:
1 - localStorage,
2 - Context API or Redux,
3 - Create a new state on your Context API or redux to store the values and pass it down to children components, in your example courses.js
If I understand correctly you want to switch between viewing a course and editing/updating a course?
One way to achieve this is:
const Course = ({ course }) => {
const user = useSelector((state) => state.auth.user.uid);
const dispatch = useDispatch();
const removeCourse = (id) => {
console.log(id);
const dbtasksWrapper = database.ref().child(user).child('courses');
dbtasksWrapper
.child(id)
.remove()
.then(() => {
dispatch({ type: 'DELETE_COURSE', id });
dispatch(fetchCourse(user));
});
};
// state to switch between updating the course and viewing the course
const [isUpdating, setIsUpdating] = useState(false);
return (
<div>
{isUpdating ? (
{/* pass the course down and a callback to close update component */}
<AddCourse course={course} finishUpdate={() => setIsUpdating(false)} />
) : (
<Card>
<CardBody className="text-center ">
<CardText className="text-center">
<h2>CourseID: {course.courseId}</h2>
</CardText>
<CardTitle className="font-weight-bold text-center">
<h1>{course.courseTitle}</h1>
</CardTitle>
<CardText className="text-center">{course.courseDesc}.</CardText>
<Container className="text-center">
{/* Set isUpdating to true */}
<Button color="warning" onClick={() => setIsUpdating(true)}>
Update
</Button>
<Button color="danger ml-4" onClick={() => removeCourse(course.id)}>
Delete
</Button>
</Container>
</CardBody>
</Card>
)}
</div>
);
};
This will change depending on your setup, if this isn't what you wanted please provide some more details about how you would like this to function.
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'));