I'm learning React and I created a simple todo list app and I'm trying to erase the input field as I did for my onClick function on my keypress function. However, it doesn't render the same when I use setTodoInput(""); on that keypress function. It only shows the first character of the input. If I comment out setTodoInput(""); out of the keypress function, it works fine, but the input field doesn't erase. I don't understand why although I have a controlled input, it doesn't function the same. if someone can please explain, it would be appreciated.
this is my code for my App file:
import React, { useState } from "react";
import InputArea from "./InputArea";
import ToDoTask from "./ToDoTask";
function App() {
const [todoTasks, setTodoTasks] = useState([]);
function addTask(todoInput) {
setTodoTasks((prevTodoTasks) => {
return [...prevTodoTasks, todoInput];
});
}
function handleKeyPress(event) {
if (event.key === "Enter") {
setTodoTasks((prevTodoInput) => {
const newTodoInput = event.target.value;
return [...prevTodoInput, newTodoInput];
});
// const newTodoInput = event.target.value;
// setTodoTasks((prevTodoTasks) => {
// console.log(newTodoInput);
// return [...prevTodoTasks, newTodoInput];
// });
// }
}
}
function deleteTodoTask(id) {
setTodoTasks((prevTodoTasks) => {
return prevTodoTasks.filter((task, i) => {
return i !== id;
});
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<InputArea onAdd={addTask} onKeyPress={handleKeyPress} />
</div>
<div>
<ul>
{todoTasks.map((todoTasks, i) => (
<ToDoTask
key={i}
id={i}
text={todoTasks}
onChecked={deleteTodoTask}
/>
))}
</ul>
</div>
</div>
);
}
export default App;
I also created an input component:
import React, { useState } from "react";
function InputArea(props) {
const [todoInput, setTodoInput] = useState("");
function handleChange(event) {
const newInput = event.target.value;
setTodoInput(newInput);
}
return (
<div className="form">
<input
onKeyDown={(event) => {
props.onKeyPress(event);
setTodoInput("");
}}
onChange={handleChange}
type="text"
value={todoInput}
/>
<button
onClick={() => {
props.onAdd(todoInput);
setTodoInput("");
}}
>
<span>Add</span>
</button>
</div>
);
}
export default InputArea;
this is my todoTask component:
import React from "react";
function ToDoTask(props) {
return (
<div
onClick={() => {
props.onChecked(props.id);
}}
>
<li>{props.text}</li>
</div>
);
}
export default ToDoTask;
If the goal is to clear the input when "enter" is pressed then I suggest using a form element. So long as there is just the one input then pressing enter while focused will submit the form. Use the form's submit handler to call the onAdd callback and reset the local todoInput state.
InputArea
function InputArea({ onAdd }) {
const [todoInput, setTodoInput] = useState("");
const submitHandler = (e) => {
e.preventDefault();
if (todoInput) {
onAdd(todoInput);
setTodoInput("");
}
};
function handleChange(event) {
const { value } = event.target;
setTodoInput(value);
}
return (
<form onSubmit={submitHandler}>
<input onChange={handleChange} type="text" value={todoInput} />
<button type="submit">
<span>Add</span>
</button>
</form>
);
}
Demo
function InputArea({ onAdd }) {
const [todoInput, setTodoInput] = React.useState("");
const submitHandler = (e) => {
e.preventDefault();
if (todoInput) {
onAdd(todoInput);
setTodoInput("");
}
};
function handleChange(event) {
const { value } = event.target;
setTodoInput(value);
}
return (
<form onSubmit={submitHandler}>
<input onChange={handleChange} type="text" value={todoInput} />
<button type="submit">
<span>Add</span>
</button>
</form>
);
}
function ToDoTask(props) {
return (
<div
onClick={() => {
props.onChecked(props.id);
}}
>
<li>{props.text}</li>
</div>
);
}
function App() {
const [todoTasks, setTodoTasks] = React.useState([]);
function addTask(todoInput) {
setTodoTasks((prevTodoTasks) => {
return [...prevTodoTasks, todoInput];
});
}
function deleteTodoTask(id) {
setTodoTasks((prevTodoTasks) => {
return prevTodoTasks.filter((task, i) => {
return i !== id;
});
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<InputArea onAdd={addTask} />
</div>
<div>
<ul>
{todoTasks.map((todoTasks, i) => (
<ToDoTask
key={i}
id={i}
text={todoTasks}
onChecked={deleteTodoTask}
/>
))}
</ul>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />
Related
import React, { useState, useEffect } from "react";
import "./style.css";
const getLocalItem = () => {
let list = localStorage.getItem("lists");
console.log(list);
if (list) {
return JSON.parse(list);
} else {
return [];
}
};
function App() {
const [text, setText] = useState("");
const [task, setTask] = useState(getLocalItem());
const changeText = (e) => {
setText(e.target.value);
};
const submitHandler = (e) => {
console.log("submited");
e.preventDefault();
setTask([...task, text]);
setText("");
};
const removeTask = (a) => {
const finalData = task.filter((curEle, index) => {
return index !== a;
});
setTask(finalData);
};
useEffect(() => {
localStorage.setItem("lists", JSON.stringify(task));
}, [task]);
return (
<>
<form onSubmit={submitHandler} className='form'>
<div className="action" >
<div >
<input
className="input"
type="text"
value={text}
onChange={changeText}
placeholder='add task...'
/>
</div>
<button type="submit" className="button" >
Add todo
</button>
</div>
<div className="listsData">
{task.map((value, index) => {
return (
<>
<div key={index}>
{value}
</div>
</>
);
})}
</div>
</form>
</>
);
}
export default App;
On adding each item I want a different color for each list. Currently, I am fetching list data from localstorage while fetching also it should remain same. which is working but the dynamic colors is what I need for each list. Any ideas or dynamic logics??
Let me know if u need more details regarding my code if u doont understand something
My question is how can I send the input value to the parent component by clicking on the button? Because now if I type something in the input it shanges the value instantly, I want it to do after I click on the button.
Currently I am using that method:
const FormInput = ({setIpAddress}) => {
return (
<div className="formInput">
<form className="form_container" onSubmit={e => {e.preventDefault();}}>
<input type="text" id="input" onChange={(e) => setIpAddress(e.target.value)} required={true} placeholder="Search for any IP address or domain"/>
<button type="submit" className="input_btn">
<img src={arrow} alt="arrow"/>
</button>
</form>
</div>
);
};
export default FormInput
You can pass an onClick callback function to the child component. When this function is called it will trigger a rerender in the child.
Example:
Parent:
const handleClick = (value) => {
//set the state here
}
<ChildComponent onClick={handleClick} />
Child:
<button type="submit" className="input_btn" onClick={(value) => props.onClick?.(value)}>
In your case you need to get rid of the onChange in your input tag:
parents:
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
try {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip);
});
};
getData();
} catch (error) {
console.trace(error);
}
}, [url]);
const handleClick = (event) => {
setIpAddress(event.target.value)
}
return (
<div className="App">
<SearchSection onClick={handleClick} />
</div>
);
}
const SearchSection = ({onClick}) => {
return (
<div className="search_container">
<h1 className="search_heading">IP Address Tracker</h1>
<FormInput onClick={onClick}/>
</div>
);
};
Child
const FormInput = ({onClick}) => {
return (
<div className="formInput">
<form className="form_container" onSubmit={e => {e.preventDefault();}}>
<input type="text" id="input" required={true} placeholder="Search for any IP address or domain"/>
<button type="submit" className="input_btn" onClick={(e) => onClick(e}>
<img src={arrow} alt="arrow"/>
</button>
</form>
</div>
);
};
Thank you for your answer, but I don't really get it, bcs my parent component has no paramter, sorry I am new in react.
This is my parent component where I am fetching the data and I want to update the ipAdress when I click on the button which is in the FormInput component. So the SearchSection is the parent of the FormInput.
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip)
...
getData();
}, [url]);
return (
<div className="App">
<SearchSection setIpAddress={setIpAddress} />
</div>
);
}
I hope it's enough :)
const SearchSection = ({setIpAddress}) => {
return (
<div className="search_container">
<h1 className="search_heading">IP Address Tracker</h1>
<FormInput setIpAddress={setIpAddress}/>
</div>
);
};
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
try {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip);
});
};
getData();
} catch (error) {
console.trace(error);
}
}, [url]);
return (
<div className="App">
<SearchSection setIpAddress={setIpAddress} />
</div>
);
}
The stack I am currently using is:
React, React-redux, styled-components, css3
I'm writing a natural responsive login form, and I'm trying to fix it with hooks while watching a course.
constructor(props) {
super(props);
this.state = {
isLogginActive: true
};
}
componentDidMount() {
//Add .right by default
this.rightSide.classList.add("right");
}
changeState() {
const { isLogginActive } = this.state;
if (isLogginActive) {
this.rightSide.classList.remove("right");
this.rightSide.classList.add("left");
} else {
this.rightSide.classList.remove("left");
this.rightSide.classList.add("right");
}
this.setState(prevState => ({ isLogginActive: !prevState.isLogginActive }));
}
render() {
const { isLogginActive } = this.state;
const current = isLogginActive ? "Register" : "Login";
const currentActive = isLogginActive ? "login" : "register";
return (
<div className="App">
<div className="login">
<div className="container" ref={ref => (this.container = ref)}>
{isLogginActive && (
<Login containerRef={ref => (this.current = ref)} />
)}
{!isLogginActive && (
<Register containerRef={ref => (this.current = ref)} />
)}
</div>
<RightSide
current={current}
currentActive={currentActive}
containerRef={ref => (this.rightSide = ref)}
onClick={this.changeState.bind(this)}
/>
</div>
</div>
);
}
}
const RightSide = props => {
return (
<div
className="right-side"
ref={props.containerRef}
onClick={props.onClick}
>
<div className="inner-container">
<div className="text">{props.current}</div>
</div>
</div>
);
};
export default App;
I'm working on the code from the lecture with hooks, but I'm starting to get confused about how to write a ref in the DOM.
export default function Modal() {
const dispatch = useDispatch();
const { isModal } = useSelector((state) => state.modal_Reducer);
const mainRef = useRef();
const rightRef = useRef();
const [isActive, setIsActive] = useState(true);
useEffect(() => {
rightRef.classList.add("right");
}, []);
const changeAuth = () => {
if (isActive) {
rightRef.classList.remove("right");
rightRef.classList.add("left");
} else {
rightRef.classList.remove("left");
rightRef.classList.add("rignt");
}
setIsActive(!isActive);
};
const onHideModal = () => {
dispatch(hideModal());
};
if (!isModal) {
return null;
}
const switchToSignup = isActive ? "Register" : "Login";
const switchToSignin = isActive ? "Login" : "Register";
return (
<ModalBackground>
<Main_Container>
<Auth_box ref={mainRef}>
{}
{}
</Auth_box>
<RightSide
ref={rightRef}
switchLogin={switchToSignin}
switcReg={switchToSignup}
onClick
/>
</Main_Container>
</ModalBackground>
It is a modal component that converts to the signup form while css animation effect occurs when the signup button is pressed in the login form.
I used useRef, but I think it's not right to use classList.add and .remove, so I need to fix it, but I'm not sure how to do it. Help.
useEffect(() => {
//rightRef.classList.add("right");
}, []);
const changeAuth = () => {
/* if (isActive) {
rightRef.classList.remove("right");
rightRef.classList.add("left");
} else {
rightRef.classList.remove("left");
rightRef.classList.add("rignt");
}
*/
setIsActive(!isActive);
};
and then
<RightSide
clsName={isActive? "right":"left"}
switchLogin={switchToSignin}
switcReg={switchToSignup}
onClick
/>
and update your component
const RightSide = props => {
return (
<div
className={`right-side ${props.clsName}`}
onClick={props.onClick}
>
<div className="inner-container">
<div className="text">{props.current}</div>
</div>
</div>
);
};
You can also explore https://www.npmjs.com/package/classnames npm package
useEffect(() => {
//rightRef.classList.add("right");
}, []);
const changeAuth = () => {
/* if (isActive) {
rightRef.classList.remove("right");
rightRef.classList.add("left");
} else {
rightRef.classList.remove("left");
rightRef.classList.add("rignt");
}
*/
setIsActive(!isActive);
};
and then
<RightSide
clsName={isActive? "right":"left"}
switchLogin={switchToSignin}
switcReg={switchToSignup}
onClick
/>
First, I set up a frame and kept working.
export default function Modal() {
const dispatch = useDispatch();
const { isModal } = useSelector((state) => state.modal_Reducer);
const mainRef = useRef();
const authRef = useRef();
const [isActive, setIsActive] = useState(true);
useEffect(() => {}, []);
const changeAuth = () => {
if (isActive) {
} else {
}
setIsActive(!isActive);
};
const onHideModal = () => {
dispatch(hideModal());
};
if (!isModal) {
return null;
}
const switchToSignup = isActive ? "Register" : "Login";
const switchToSignin = isActive ? "Login" : "Register";
return (
<ModalBackground>
<Main_Container ref={mainRef}>
<Auth_box>
{isActive && <Login authRef={authRef} />}
{!isActive && <Register authRef={authRef} />}
</Auth_box>
</Main_Container>
<RightSide
onSwitch={isActive ? "right" : "left"}
switchSignIn={switchToSignin}
switchRegister={switchToSignup}
onClick={changeAuth}
/>
</ModalBackground>
);
}
function RightSide(props) {
return (
<Right_Side
className={`right-side ${props.onSwitch}`}
ref={props.mainRef}
onClick={props.changeAuth}
>
<div className="inner-container">
<div className="text">{props.switchRegister}</div>
</div>
</Right_Side>
);
}
The problem is the login form conversion page, but the original uses ref and passes it as props.
login and register transfer current as ref.
But I don't understand why the container passes this.container.
original
export class Login extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="base-container" ref={this.props.containerRef}>
<div className="header">Login</div>
<div className="content">
<div className="image">
<img src={loginImg} />
</div>
<div className="form">
<div className="form-group">
<label htmlFor="username">Username</label>
<input type="text" name="username" placeholder="username" />
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input type="password" name="password" placeholder="password" />
</div>
</div>
</div>
<div className="footer">
<button type="button" className="btn">
Login
</button>
</div>
</div>
);
}
my code
function Login(props) {
const dispatch = useDispatch();
const history = useHistory();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [submited, setSubmited] = useState(false);
const [showPwd, setShowPwd] = useState(false);
const [alert, setAlert] = useState("");
const formInputValue = (key) => (e) => {
if (key === "EMAIL") setEmail(e.target.value);
if (key === "PASSWORD") setPassword(e.target.value);
console.log(e.target.value);
};
const onLogin = async (e) => {
e.preventDefault();
if (!email || !password) {
setAlert("이메일 주소와 비밀번호를 입력 하세요!");
}
let data = {
email: email,
password: password,
};
dispatch(loggedIn(data));
history.push("/");
};
return (
<PanelContainer ref={props.authRef}>
<h1>로그인</h1>
<PanelContent>
<Form onSubmit={onLogin}>
<InputGroup>
<label htmlFor="email">E-Mail</label>
<Input
type="text"
name="email"
value={email}
onChange={formInputValue("EMAIL")}
placeholder="E-mail"
/>
</InputGroup>
<InputGroup>
<label htmlFor="password">Password</label>
<Input
type={showPwd ? "text" : "password"}
name="password"
value={password}
onChange={formInputValue("PASSWORD")}
placeholder="Password"
/>
</InputGroup>
<SubmitBtn> 로그인 </SubmitBtn>
</Form>
</PanelContent>
</PanelContainer>
);
}
export default Login;
I have a simple feedback application that contains multiple component.
app.js - parent component
data.js - this contain a dummy data the have name and the listofreview
component
user - this component will display the name and the thumbnail
review - this will display the list of review about the user and also have button that say leave a review.
modal - after the user click the leave a review this modal will appear that have a list of element that will update the list of review
I used useReducer to update the state. But the problem is the review.js don't show the updated state. maybe because the useReducer is located on the modal.js. What should i do so that i can also update the data that been display on the review.js
App.js
function App() {
return (
<div className="main-container">
<DisplayUser />
<div className="mainContent-container">
<DisplayReview />
</div>
</div>
);
}
User.js
import { data } from '../../src/data';
const CommentHandler = () => {
const [user] = React.useState(data);
return (
<>
{user.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
Review.js
import DisplayModal from './Modal'
import Modal from 'react-modal';
import { useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
const ReviewHandler = () => {
const [user] = useState(data);
const [showModal, setShowModal] = useState(false);
return (
<>
{user.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
Modal.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, data);
const [hoverRating, setHoverRating] = useState(null);
const handelSubmit = (e) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { reviewId: new Date().getTime(), name, occupation, rating, reviews };
dispatch({
type: 'ADD_REVIEW_ITEM',
payload: newReview
});
}
}
return (
<div className="modal-container">
<div className="modal-backdrop">
<form className="modal-inner" onSubmit={handelSubmit}>
<h2>Feel feel to send us your review!</h2>
<div className='revieweRating-container'>
<h3>How was your experience?</h3><p onClick={() => props.onClick(false)}>X</p>
<div className="dynamic-review">
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return (
<label>
<input type="radio"
name="review-star"
value={ratingValue}
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
onClick={() => setRating(ratingValue)}>
</input>
<FontAwesomeIcon
icon="star"
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
color={ratingValue <= (hoverRating || rating) ? "#FAD020" : "#BCC5D3"}
className="review-star" />
</label>
)
})}
</div>
</div>
<input
type="text"
name="name"
className="name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
name="occupation"
className="alioccupationas"
placeholder="Aloccupationias"
value={occupation}
onChange={(e) => setOccupation(e.target.value)}
/>
<textarea
name="review"
cols="30"
rows="6"
className="review"
placeholder="Enter your review here!"
value={reviews}
onChange={(e) => setReviews(e.target.value)}>
</textarea>
<button type="submit" className="submit">SEND A REVIEW</button>
</form>
<div>
{state.map((data) => (
<div key={data.id}>
{data.listOfReview.map((review) => (
<div key={review.reviewId}>
<h3>{review.name}</h3>
<p>{review.occupation}</p>
</div>
))}
</div>
))}
</div>
</div>
</div >
);
}
reducer.js
export const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
console.log(state);
return state.map((data) => {
if (data) {
const newReview = [...data.listOfReview, action.payload];
return {
...data,
listOfReview: newReview
};
}
return data;
});
default:
return state;
}
};
data.js
export const data = [
{
id: 1607089645363,
name: 'Andress Bonifacio',
noOfReview: 1,
listOfReview: [
{
reviewId: 1607089645361,
name: 'Juan Dela Cruz',
occupation: 'Father of Phil. Revolution',
rating: 5,
review: 'Numquam labore or dolorem enim but accusantium and autem ratione.',
}
]
}
];
If you want to get the updated data across all component, then make sure to have this line of code const [state, dispatch] = useReducer(reducer, data); available in every component that use them like in:-
in User.js
import { data } from '../../src/data';
import { reducer } from './reducer';
const CommentHandler = () => {
// not this
const [user] = React.useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
return (
<>
{state.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
in Review.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ReviewHandler = () => {
// not this
// const [user] = useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
const [showModal, setShowModal] = useState(false);
return (
<>
{state.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
I would suggest you try and use context api instead of just relying on useReducer.
I'm experimenting on a custom modal using React.createPortal, everything's working if I am only opening and closing the modal via buttons inside the modal and the default result is displayed on the component below but what I want to achieve is when I click the modal container (dark overlay div) it should close the modal, so I added an exithandler on it, but the problem is the default form button behavior inside the modal content doesn't work anymore even if there's an event.stopPropagation() function in the exithandler.
Here's the codesandbox sample of two modals, one with a form and one without: https://codesandbox.io/s/react-modals-2dnei
Modal container:
import React from "react";
import { createPortal } from "react-dom";
const modalRoot = document.getElementById("modal-root");
function Modal({ exitHandler, children }) {
return createPortal(
<div className="modal" onClick={exitHandler}>
{children}
</div>,
modalRoot
);
}
export default Modal;
Modal content 1:
import React from "react";
const HelloWorld = ({ user, onClose }) => {
return (
<div className="modal-content">
<div className="modal-header">
<div className="modal-title">
<h2>Greetings</h2>
</div>
<div className="modal-action">
<button type="button" onClick={onClose}>X</button>
</div>
</div>
<div className="modal-body">
<p>{user ? `Hello ${user}!` : "Greetings!"}</p>
</div>
</div>
);
};
export default HelloWorld;
Modal Content 2:
import React, { forwardRef, useEffect } from "react";
const SignUp = forwardRef((props, ref) => {
const { onClose, onSignUp, handleChange, inputError, user } = props;
useEffect(() => {
ref.current.focus();
});
return (
<div className="modal-content">
<div className="modal-header">
<div className="modal-title">
<h2>Sign Up</h2>
</div>
<div className="modal-action">
<button type="button" onClick={onClose}>X</button>
</div>
</div>
<div className="modal-body">
<form onSubmit={onSignUp} className="modal-form">
<label className="input-label">
<span>Name:</span>
<input
type="text"
onChange={handleChange}
value={user}
ref={ref}
className={inputError === true ? "input-error" : null}
/>
</label>
<input className="btn" type="submit" value="Submit" />
</form>
</div>
</div>
);
});
export default SignUp;
Main:
const App = () => {
const [modalState, setModalState] = useState(false);
const [modalId, setModalId] = useState(null);
const [inputError, setInputError] = useState(false);
const [user, setUser] = useState("");
const [userStatus, setUserStatus] = useState("");
const nameRef = useRef(null);
const handleShowModal = (e) => {
const modalId = e.target.id.toString();
return [setModalState(true), setModalId(modalId)];
};
const handleHideModal = (event) => {
event.stopPropagation(); // This doesn't work
setModalState(false);
};
const runSignUpResult = useCallback(() => {
handleHideModal();
setUserStatus(`Thank you ${user} for signing up!`);
}, [user]);
const handleValidation = useCallback(
(nameParameter) => {
if (nameParameter.length === 0) {
nameRef.current.focus();
setInputError(true);
} else {
return [setInputError(false), runSignUpResult()];
}
},
[runSignUpResult]
);
const handleSignUp = useCallback(
(e) => {
e.preventDefault();
const name = nameRef.current.value;
handleValidation(name);
},
[nameRef, handleValidation]
);
const handleChange = (e) => {
setUser(e.target.value);
};
const modal = modalState ? (
<Modal exitHandler={handleHideModal}>
{modalId === "greeting" ? (
<HelloWorld onClose={handleHideModal} user={user} />
) : (
<SignUp
onClose={handleHideModal}
onSignUp={handleSignUp}
handleChange={handleChange}
user={user}
ref={nameRef}
inputError={inputError}
modalId={modalId}
/>
)}
</Modal>
) : null;
return (
<div className="App">
<AppHeader />
<main className="app-body">
<section className="modal-btns">
<button id="greeting" className="btn" onClick={handleShowModal}>
Greetings!
</button>
<button id="signup" className="btn" onClick={handleShowModal}>
Sign Up
</button>
</section>
<section>{modal}</section>
<section className="user-status">
<h3>{user.length === 0 ? null : userStatus}</h3>
</section>
</main>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);