I am trying to use Reactstrap 8.5.1 innerRef attribute along with useRef() to focus the Input within a Modal whenever the Modal opens. The code below shows the Button which opens the Modal, but when clicked I get the error "Cannot read property 'focus' of null". It also writes inputRef to the console, which shows that .current is null.
I've tried various ways to set innerRef, but nothing seems to work. I'd be very grateful if someone can point out to me what I am missing.
import React, { useState, useRef, useEffect } from 'react';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Input } from 'reactstrap';
export const ModalSave = (props) => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const toggle = () => setModalIsOpen(!modalIsOpen);
const inputRef = useRef(null);
useEffect(() => {
console.log(inputRef);
if (modalIsOpen === true) {
inputRef.current.focus();
}
}, [modalIsOpen]);
return (
<div>
<Button
onClick={() => { setModalIsOpen(true); }}
>Save</Button>
<Modal isOpen={modalIsOpen} toggle={toggle}>
<ModalHeader toggle={toggle}>Save</ModalHeader>
<ModalBody>
Name:
<Input
innerRef={inputRef.current}
/>
</ModalBody>
<ModalFooter>
<Button>Save</Button>
<Button onClick={toggle}>Close</Button>
</ModalFooter>
</Modal>
</div>
);
}
The issue is that opening the modal doesn't trigger the component to re-render which is needed to get the input ref value, and so, the ref will remain null unless some state is called to trigger the re-render. As a workaround, you can use setTimeout() method to kind of force it like so:
useEffect(() => {
if (modalIsOpen) {
setTimeout(() => inputRef.current.focus(), 0);
}
}, [modalIsOpen]);
A better solution is to use the onOpened method which is called after the modal has opened:
export default function App() {
const inputRef = useRef(null);
const [modalIsOpen, setModalIsOpen] = useState(false);
const toggle = () => setModalIsOpen(!modalIsOpen);
const handleOpen = () => inputRef.current.focus();
return (
<div className="App">
<div>
<Button onClick={() => setModalIsOpen(true)}>Save</Button>
<Modal isOpen={modalIsOpen} toggle={toggle} onOpened={handleOpen}>
<ModalHeader toggle={toggle}>Save</ModalHeader>
<ModalBody>
Name:
<Input innerRef={inputRef} />
</ModalBody>
<ModalFooter>
<Button>Save</Button>
<Button onClick={toggle}>Close</Button>
</ModalFooter>
</Modal>
</div>
</div>
);
}
Related
I'm trying to build a table component and make one of its cells editable.
I need this cell to be clickable, and if clicked, an input component would replace the button, and it would get focused automatically so that users can decide the text of this cell.
Now in the first rendering, button would be rendered, which leads to the binding ref of Input failing.
Here is my simplified code:
import { Input, InputRef, Button } from 'antd'
import { useRef, useState, useEffect } from 'react'
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef<InputRef>(null)
useEffect(() => {
console.log(inputRef.current)
}, [inputRef, showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => {
setIsShowInput(true)
if (showInput) inputRef.current?.focus()
}}>Edit me</Button>}
</div>
);
}
How can I make the binding of ref takes effect in the first rendering, so when I click the button, Input would get focused.
Or is there any other way to achieve this?
The easiest way to achieve this is to watch the showInput value. If the value is true then call the focus method, otherwise do nothing as the Input component will be unmounted from the App.
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef(null)
useEffect(() => {
if (!showInput) return;
inputRef.current.focus()
}, [showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => setIsShowInput(true)}>Edit me</Button>}
</div>
);
}
I Am failing to get the radio button selected when I press the Enter key
I tried this code here :
import { useState, useEffect } from 'react';
const HandleKeypress = () =\> {
const [itWorks, setItWorks] = useState(true)
useEffect(() => {
document.addEventListener('keypress', (e) => {
if (e.code === 'Enter') setItWorks(!itWorks)
e.preventDefault();
})
}, [])
return (
<div>
<p>{itWorks ? 'It works!' : 'It does not'}</p>
<button onClick={() => setItWorks(!itWorks)} >Press me</button>
<input type='radio' aria-selected onKeyPress={() => this.HandleKeypress } />
</div>
)
}
export default HandleKeypress;
You dont have to use onKeyPress() use onClick() instead.
You are using functional component, so, do
<input type='radio' aria-selected onClick={handleClick} />
You cant call the main component which you are working on.
So, the solution for this is
import { useState, useEffect } from 'react';
const HandleKeypress = () => {
const [itWorks, setItWorks] = useState(false)
function handleClick(){
SetItWorks(true)
}
return (
<div>
<p>{itWorks ? 'It works!' : 'It does not'}</p>
<button onClick={() =>
setItWorks(!itWorks)} >Press me</button>
<input type='radio' aria-selected onClick={handleClick} />
</div>
)
}
export default HandleKeypress;
This will work.
I have to add and edit person's experience details through reactjs popups, so that I have Experience component, ExperienceAddPopup component, and ExperienceEditPopup component. So on each click on add(+) button there appears a popup to add person's experience details. This thing is working fine.
Also on click of edit button popup shows but without datas. Now my issue is all the added details I have to edit on click on each edit button (pencil image) based on each clicked id's (not working). how to pass ids on edit modal popups here on edit click?
Here is my code
Experience.js component
import React, {useState, useEffect, Component } from "react";
import axios from 'axios';
import { Modal } from 'react-bootstrap';
import ExperienceAddPopup from "./ExperiencePopup";
import ExperienceEditPopup from "./ExperienceEditPopup";
export default function Experience({logged_user}) {
const [modalShow, setModalShow] = React.useState(false);
const [show, setShow] = useState(false);
return (
<div className="exp-details">
{experienceArray.map((experience, i) => {
return(
<span key={experience.id}>
<div className="exph1">{experience.title} <span onClick={() => setModalShow(true)}><FontAwesomeIcon className="nav-icon float-right text-muted" icon={faPencilAlt} /></span></div>
<ExperienceEditPopup logged_user={singleUserDetail.actable_id}
show={modalShow}
onHide={() => setModalShow(false)}
/>
<div className="exph2">{experience.company_name}.</div>
<div className="exph3">{experience.start_year} - {experience.end_year}</div>
<hr/>
</span>
);
})
}
</div>
)
}
ExperienceEditPopup.js
import React, {useState, useEffect, Component } from "react";
import axios from 'axios';
import { Modal } from 'react-bootstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faPencilAlt} from '#fortawesome/free-solid-svg-icons';
const ExperienceEditPopup = props => {
#how to get edit clicked ids based popups here?
let logged_user_id = props.logged_user;
let experience_id = # here i need to get the expereince id on each popup click
const [show, setShow] = useState(false);
const handleClose = () => setShow(false); #here i need to close popups based on edit button click
const handleShow = () => setShow(true); #here i need to show popups based on edit button click
const [experienceArray, setexperienceArray] = React.useState([]);
const getexperienceUserDetails = (experience_id, logged_user_id) => {
axios
.get(`http://localhost:3001/users/${logged_user_id}/experiences/${experience_id}`, { withCredentials: true })
.then((response) => {
const experienceArray = response.data;
setexperienceArray(experienceArray);
console.log(experienceArray);
})
.catch((error) => {
console.log(" error", error);
});
};
React.useEffect(() => {
if (experience_id) {
getexperienceUserDetails(experience_id, logged_user_id);
}
}, [experience_id,logged_user_id]);
return (
<Modal {...props} size="lg" aria-labelledby="contained-modal-title-vcenter" centered >
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLongTitle">Edit Experience</h5>
<button type="button" className="close" onClick={props.onHide} aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<form onSubmit={handleExperienceEditSubmit}>
<div className="modal-body">
<input type="text" class="form-control" defaultValue={experienceArray.title} name="title" onChange={handleChange} placeholder="Designation" />
<input type="text" class="form-control" defaultValue={experienceArray.company_name} name="company_name" onChange={handleChange} placeholder="Company Name" />
<input type="text" defaultValue={experienceArray.location} class="form-control" name="location" onChange={handleChange} placeholder="Location"></input>
</div>
<div className="modal-footer">
<span className="btn_cls"><button className="btn save-btn">Save</button></span>
</div>
</form>
</Modal>
);
}
export default ExperienceEditPopup;
You have a single state for all Popups.
const [modalShow, setModalShow] = React.useState(false);
You could store the experience.id so you could render the right modal.
const [modalShow, setModalShow] = React.useState(-1);
Also, you will need to change the trigger function
<span onClick={() => setModalShow(experience.id)}>
You may pass the experience_id here
<ExperienceEditPopup
logged_user={singleUserDetail.actable_id}
show={modalShow}
onHide={() => setModalShow(false)}
/>
Something like:
<ExperienceEditPopup
logged_user={singleUserDetail.actable_id}
experience_id={experience.id}
show={modalShow === experience.id}
setModalShow={setModalShow}
onHide={() => setModalShow(-1)}
/>
Inside your ExperienceEditPopup you don't need to control the visibility of it, so you don't need:
const [show, setShow] = useState(false)
Instead of that you could use the setModalShow that you might pass as a props to it.
I think you didn't really understand the code you wrote, ask to clarify your mind before asking for solutions, it's always a better approach
I'm trying to build an input component with a clear button using react#17
import { useRef } from 'react';
const InputWithClear = props => {
const inputRef = useRef();
return (
<div>
<input
ref={inputRef}
{...props}
/>
<button
onClick={() => {
inputRef.current.value = '';
inputRef.current.dispatchEvent(
new Event('change', { bubbles: true })
);
}}
>
clear
</button>
</div>
);
};
using this component like:
<InputWithClear value={value} onChange={(e) => {
console.log(e); // I want to get a synthetic event object here
}} />
but the clear button works once only when I did input anything first, and stop working again.
if I input something first and then click the clear button, it does not work.
why not using?
<button
onClick={() => {
props.onChange({
target: { value: '' }
})
}}
>
clear
</button>
because the synthetic event object will be lost
So, how do I manually trigger a synthetic change event of a react input component?
Try this approach,
Maintain state at the parent component level (Here parent component is App), onClear, bubble up the handler in the parent level, and update the state.
import React, { useState } from "react";
import "./styles.css";
const InputWithClear = (props) => {
return (
<div>
<input {...props} />
<button onClick={props.onClear}>clear</button>
</div>
);
};
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<InputWithClear
value={value}
onChange={(e) => {
console.log(e); // I want to get a synthetic event object here
setValue(e.target.value);
}}
onClear={() => {
setValue("");
}}
/>
</div>
);
}
Working code - https://codesandbox.io/s/youthful-euler-gx4v5?file=/src/App.js
you should use state to control input value rather than create useRef, that's the way to go. you can use a stopPropagation prop to control it:
const InputWithClear = ({value, setValue, stopPropagation = false}) => {
const onClick = (e) => {
if(stopPropagation) e.stopPropagation()
setValue('')
}
return (
<div>
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
<button
onClick={onClick}
>
clear
</button>
</div>
);
};
export default function App() {
const [value, setValue] = useState('')
return (
<div className="App">
<InputWithClear value={value} setValue={setValue} stopPropagation />
</div>
);
}
I am using hook in component to manage modal state.
(Class version of component reproduce the problem)
handleClick will open modal and handleModalClose should close.
I send handleModalClose to Modal component and with console.log could see, that it is processed, but the isModalOpen state not changed (the same for callback setState).
When I am trying to invoke it with setTimeout - state changes and Modal is closing.
Why the hell the state not changes when I invoke changing from child???
const [isModalOpen, setModalOpen] = useState(false);
const handleClick = () => {
setModalOpen(true);
// setTimeout(() => handleModalClose, 10000);
};
const handleModalClose = () => {
console.log('!!!!!!!!handleModalClose');
setModalOpen(false);
};
return (
<div onClick={handleClick}>
{isModalOpen && <Modal closeModal={handleModalClose} />}
</div>
);
and here is extract from Modal
const Modal = (props) => {
const { closeModal } = props;
return (
<>
<div className="modal">
<form className="" onSubmit={handleSubmit(onSubmit)}>
<button type="button" className="button_grey button_cancel_modal" onClick={closeModal}>
</button>
PROBLEM SOLVED. e.stopPropagation() - added.
const handleModalClose = (e) => {
e.stopPropagation();
console.log('!!!!!!!!handleModalClose');
setModalOpen(false);
};
Modal was closed and instantly reopen by bubbling w/o this.