How to go to last element in react? - javascript

I tried scrollIntoView and used .lastchild but I can't go to the latest comment. Used window.scrollTo too can't find any solution.
Can anyone have some suggestions on what will work?
const click = () => {
fetch('url')
.then(res => res.json())
.then(data => {
if (data.affectedRows > 0) {
var element = document.getElementById("txt").lastChild;
element.scrollIntoView({behavior: "smooth", block: "end", inline: "center"});
}
})
}
}
{getComment.map(cmnt =>
<div key={cmnt.comment_id} className="article-comments-reply">
<div className="text-box" id="txt">
<h3>{cmnt.username}</h3>
<div className="reply-box">
<h4>{cmnt.comment_text}</h4>
</div>
</div>
</div>
)}

You don't need to use last child. It is also not the best solution in React to use tools to get an element by selector.
Just set the parent element's scrollTop to its height.
It looks like this:
ref.current.scrollTop = ref.current.scrollHeight;
And this ref should point to your parent component in which this code is wrapped:
{getComment.map(cmnt =>
<div key={cmnt.comment_id} className="article-comments-reply">
<div className="text-box" id="txt">
<h3>{cmnt.username}</h3>
<div className="reply-box">
<h4>{cmnt.comment_text}</h4>
</div>
</div>
</div>
)}
If you want to add scrollBehavior via js, you can use something like this:
ref.current.style.scrollBehavior = 'smooth';
As a result, it can be easily transferred into a custom hook that can perform smooth scrolling on any element.
Custom hook useScrollToBottom.
export const useScrollToBottom = (ref) => {
const scrollToBottom = () => {
ref.current.style.scrollBehavior = 'smooth';
ref.current.scrollTop = ref.current.scrollHeight;
};
return {
scrollToBottom,
}
}
Using
// ...
const ref = useRef();
const {scrollToBottom} = useScrollToBottom(ref);
useEffect(() => {
// scroll when you need
scrollToBottom();
}, []);
return (
<div ref={ref} className="list">
{
// some elements
}
</div>
)
// ...

You can use this function from any component you want this code will target your last child.
const scrollToBottom = () => {
let targetLastElement = document.getElementsByClassName('classNameOfTileWhichYouWantToTarget');
targetLastElement = targetLastElement[targetLastElement.length - 1]
targetLastElement.scrollIntoView({ behavior: "smooth" });
}

Related

How to change the background Color of selected item in a list using ReactJS

Here I need to change the background color of an selected item from a list and get the value of the selected Item as well , Please Can anyone help me in this.. Thanks in Advance
Here is my Component
const Time = () => {
const dataTime = useMemo(() => TimeData, []);
const chunks = useMemo(() => {
const _chunks = [];
const tmp = [...dataTime];
while (tmp.length) {
_chunks.push(tmp.splice(0, 3));
}
//console.log(_chunks);
return _chunks;
}, [dataTime]);
return (
<div className="Main-Section">
<div className="First-Section">Time</div>
<div className="Second-Section">
<div className="date_title">Wednesday , June 13</div>
<div className="time_Slots">
{chunks.map((timeslot) => {
return (
<div key={timeslot} className="inline">
{timeslot.map((time) => {
return <p className='timeslots' key={time}>{time}</p>;
})}
</div>
);
})}
</div>
<div className='new_Date'>PICK A NEW DATE</div>
</div>
</div>
);
};
I beleive there may be better solutions as well, but you could add something like this to your paragraph:
{timeslot.map((time) => {
return <p
className='timeslots'
key={time}
onClick={(event) => yourHandlerFunction(event, time)}
>
{time}
</p>;
})}
and then in your handler function you could add a custom class to your element e.g. selected or just simply change its background directly
const yourHandlerFunction = (event, time) => {
event.target.classList.add('selected');
// ...
};
Edit: Another approach: https://codesandbox.io/s/select-list-item-example-zu3qb7?file=/src/App.js

useState value changes when i call it from child

I'm creating a timer app built using react-hooks and an array of this timers
I don't understand why timerList changes
Here it is the parent component
const [timerList, setTimerList] = useState([]);
const removeTimer = () => {
console.log("timerList", timerList);
};
return (
<div id="main">
{timerList ? timerList.map((child) => child) : null}
<div className="add-button before">
<button
onClick={() => {
const time = new Date();
time.setSeconds(time.getSeconds() + 0);
setTimerList((timerList) => [
...timerList,
<FullTimer
expiryTimestamp={time}
removeTimer={() => {
removeTimer();
}}
id={window.prompt("Insert timer name") + ` ${timerList.length}`}
key={timerList.length}
/>,
]);
}}
>
The interested child's component part:
<button
onClick={() => {
removeTimer();
}}
>
The child component is a custom timer with some css, and when i call removeTimer the value of timerList (in the parent component) changes, when it should remain the same.
What am I missing?
P.S. the button tags aren't closed because i have some element inside them that use awesome-font
Side note: In general it's considered bad practice to store components in another components state.
But that's not really the problem here. Given your code, it's a simple closure problem.
This:
const removeTimer = () => {
console.log("timerList", timerList);
};
definition closes over the current timerList. So it will log it, as it was when removeTimer was assigned. Currently that's on every render. So it will log the state seemingly one step behind. There's no fix around that, because that's just how closures work.
Provided you actually want to remove a timer, when removeTimer is invoked, you would need to use the callback version of the updater (setTimerList) and pass some identifying value so that you can actually remove the correct one.
This would all be a lot simpler, if you followed the initial advice and don't store the component in the state, but rather it's defining properties.
The following would be a working example (please excuse my typescript):
import React, { useState } from 'react';
type FullTimerProps = {
id: string;
expiryTimestamp: Date;
removeTimer: () => void;
}
const FullTimer = ({expiryTimestamp, removeTimer, id}: FullTimerProps): JSX.Element => {
return (
<div>
<button onClick={removeTimer}>remove</button>
{id}: {expiryTimestamp.toLocaleDateString()}
</div>
);
};
type Timer = {
id: string;
expiryTimestamp: Date;
};
const TimerList = (): JSX.Element => {
const [timerList, setTimerList] = useState<Timer[]>([]);
const removeTimer = (timer: Timer) => {
setTimerList(timerList => timerList.filter(t => t.id !== timer.id));
};
return (
<div id="main">
{timerList.map(timer => (
<FullTimer
key={timer.id}
id={timer.id}
expiryTimestamp={timer.expiryTimestamp}
removeTimer={() => removeTimer(timer)}
/>
))}
<div className="add-button before">
<button
onClick={() =>
setTimerList(timerList => [...timerList, {
id: window.prompt('Insert timer name') + ` ${timerList.length}`,
expiryTimestamp: new Date()
}])}
>Add
</button>
</div>
</div>
);
};
changing this code snippet
setTimerList((timerList) => [
...timerList,
<FullTimer
expiryTimestamp={time}
removeTimer={() => removeTimer()}
id={window.prompt("Insert timer name") + ` ${timerList.length}`}
key={timerList.length}
/>,
]);
to
timerList.push(<FullTimer
expiryTimestamp={time}
removeTimer={() => removeTimer()}
id={window.prompt("Insert timer name") + ` ${timerList.length}`}
key={timerList.length}
/>);
setTimerList([...timerList]);
Fixed the problem you are having. Although this change is not recommended because it is not immutable approach, but it fixes this case.
UPDATE: It turned out that you duplicated the removeTimer function during the setTimerList call which cause the child component to capture the timerList at the moment of assignment. Which is mentioned at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures?retiredLocale=vi#closure as mr #yoshi has shown
Try to write your onclick function like this
<button
onClick={() => removeTimer()}
>
Also over here
<FullTimer
expiryTimestamp={time}
removeTimer={() => removeTimer()}

React performance problems with increasing number of components

Turns out the problem was my computer. However, James made some good points about how to isolate the problem and utilizing useCallback and useMemo to optimize.
I am having problems with the performance of my react app. For now I'm excluding the code because I feel there might be some common sense answers.
This is the demo video
Here some pointers
I don't have unnecessary re-renders. Only individual components get rendered when they are hovered.
The animations are confined to a container div of the hovered element so no re-paints happen on the page outside of that container when hovering.
I am not using any heavy code for the hover effect or detection.
I am wondering what else could be cause for performance problems like this. As far as I understand, the number of components shouldn't matter if they are just sitting there, not rerendering.
Here is the code for the card component that is being animated. I wasn't quite sure whats important to show here. The parent component showing all the cards does not re-render.
export default function CardFile(props) {
// Input field
const input = useRef(null)
//Input state
const [inputActive, setInputActive] = useState(false);
const [title, setTitle] = useState(props.file.name)
const [menuActive, setMenuActive] = useState(false)
const [draggable, setDraggable] = useState(true)
const [isDragged, setIsDragged] = useState(false)
// counter > 0 = is hovered
const [dragCounter, setDragCounter] = useState(0)
//_________________ FUNCTIONS _________________//
// Handle file delete
const handleDelete = (e) => {
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).delete().then(() => {
console.info('Deleted')
}).catch((err) => console.err(err))
}
// Prevent default if necessary
const preventDefault = (e) => {
e.preventDefault()
e.stopPropagation()
}
// Handle rename
const handleRename = (e) => {
e.stopPropagation()
setMenuActive(false)
setInputActive(true)
}
// Handle change
const handleChange = () => {
setTitle(input.current.value)
}
// Handle focus loss
const handleFocusLoss = (e) => {
e.stopPropagation()
setInputActive(false)
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
.then(() => {
console.info('Updated title')
}).catch((err) => console.error(err))
}
// Handle title submit
const handleKeyPress = (e) => {
console.log('key')
if (e.code === "Enter") {
e.preventDefault();
setInputActive(false)
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
.then(() => {
console.info('Submitted title')
}).catch((err) => console.error(err))
}
}
// Set input focus
useEffect(() => {
if (inputActive) {
input.current.focus()
input.current.select()
}
}, [inputActive])
//_____________________________DRAGGING___________________________//
//Handle drag start
const onDragStartFunctions = () => {
props.onDragStart(props.file.id)
setIsDragged(true)
}
// Handle drag enter
const handleDragEnter = (e) => {
// Only set as target if not equal to source
if (!isDragged) {
setDragCounter(dragCounter => dragCounter + 1)
}
}
//Handle drag end
const handleDragEnd = (e) => {
e.preventDefault()
setIsDragged(false)
}
// Handle drag exit
const handleDragLeave = () => {
// Only remove as target if not equal to source
if (!isDragged) {
setDragCounter(dragCounter => dragCounter - 1)
}
}
// Handle drag over
const handleDragOver = (e) => {
e.preventDefault()
}
// Handle drag drop
const onDragDropFunctions = (e) => {
setDragCounter(0)
// Only trigger when target if not equal to source
if (!isDragged) {
props.onDrop({
id: props.file.id,
display_type: 'file'
})
}
}
return (
<div
className={`${styles.card} ${dragCounter !== 0 && styles.is_hovered} ${isDragged && styles.is_dragged}`}
test={console.log('render')}
draggable={draggable}
onDragStart={onDragStartFunctions}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
onDragLeave={handleDragLeave}
onDrop={onDragDropFunctions}
>
<div className={styles.cardInner}>
<div className={styles.videoContainer} onClick={() => props.handleActiveMedia(props.file, 'show')}>
{props.file.thumbnail_url && props.file.type === 'video' &&
<MdPlayCircleFilled className={styles.playButton} />
}
{!props.file.thumbnail_url && props.file.type === 'image' &&
<MdImage className={styles.processingButton} />
}
{!props.file.thumbnail_url && props.file.type === 'video' &&
<FaVideo className={styles.processingButton} />
}
<div className={styles.image} style={props.file.thumbnail_url && { backgroundImage: `url(${props.file.thumbnail_url})` }}></div>
</div>
<div className={styles.body}>
<div className={styles.main}>
{!inputActive ?
<p className={styles.title}>{title}</p>
:
<input
ref={input}
className={styles.titleInput}
type="text"
onKeyPress={handleKeyPress}
onChange={handleChange}
onBlur={handleFocusLoss}
defaultValue={title}
/>
}
</div>
<ToggleContext onClick={() => setMenuActive(prevMenuActive => !prevMenuActive)}>
{
menuActive && <div className={styles.menuBackground} />
}
<Dropdown top small active={menuActive}>
<ButtonLight title={'Rename'} icon={<MdTitle />} onClick={handleRename} />
<ButtonLight title={'Label'} icon={<MdLabel />} onClick={() => props.handleActiveMedia(props.file, 'label')} />
<ButtonLight title={'Share'} icon={<MdShare />} onClick={() => window.alert("Sharing is not yet supported. Stay put.")} />
{/*props.file.type === 'video' && <ButtonLight title={'Split'} icon={<RiScissorsFill />} />*/}
<ButtonLightConfirm
danger
title={'Delete'}
icon={<MdDelete />}
onClick={(e) => preventDefault(e)}
confirmAction={handleDelete}
preventDrag={() => setDraggable(false)}
enableDrag={() => setDraggable(true)}
/>
</Dropdown>
</ToggleContext>
</div>
</div>
</div>
);
}
And here is the css for animating it:
.is_hovered {
box-shadow: 0 0 0 3px var(--blue);
}
.is_hovered > div {
transform: scale(0.9);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.08);
transition: .1s;
}
Edit: Added code
Edit2: Updated sample video to show re-renders
The thing I think you should try first is 'memoizing' all your functions with useCallback. Especially as you're passing some of these functions down to other components, it's possible they are causing unnecessary re-rendering deeper in the DOM.
I don't know if you're familiar with useCallback, but basically it just wraps around your function, and only updates it when specific values change. This allows React to avoid re-creating it on every render and causing components deeper in the DOM to re-render.
You can read the docs here, but the gist of it is that instead of const getA = () => a you would write getA = useCallback(() => a, [a]), and the array contains all the dependencies for the function which cause it to update if changed.
Make sure you use these in your JSX, and avoid arrow functions like onClick={(e) => preventDefault(e)}. The function you have called preventDefault can even live outside the component entirely, since it makes no reference to anything specific to the component.
Try making these updates and see if it makes a difference. Also test without the console.log, since that can also slow things down.

React scroll to anchor when opening URL in browser

Lets say I have component "Post" which holds multiple components "Comment". I want to make that application scrolls down on comment with that anchor when I enter URL like this:
/post/:postId/#commentId
I have already working postId route /post/:postId
I tried to implement it with react-hash-link npm package but it's not working as intended.
Every comment has it's own ID which is set on component, like this:
<div class="post">
<div class="post-header">
<div class="post-header-avatar">
SOME TEXT
</div>
<div class="post-header-info">
SOME TEXT
</div>
</div>
<div class="post-content">
<span>POST CONTENT</span>
</div>
<div class="post-likers-container">
<div class="post-likers-header label">People who like this post</div>
<div class="post-likers">
SOME TEXT
</div>
</div>
<div class="post-comments">
<div class="comments ">
<div class="comments-all label">Comments</div>
<div class="comments">
<div class="comment" id="5d27759edd51be1858f6b6f2">
<div class="comment-content">
COMMENT 1 TEXT
</div>
</div>
<div class="comment" id="5d2775b2dd51be1858f6b720">
<div class="comment-content">
COMMENT 2 TEXT
</div>
</div>
<div class="comment" id="5d2775ecdd51be1858f6b753">
<div class="comment-content">
COMMENT 3 TEXT
</div>
</div>
</div>
</div>
</div>
</div>
So for example if I open URL like:
/post/postId/#5d2775ecdd51be1858f6b753
I want to open page of post and that it scrolls down to the comment with # anchor.
Is there any way to implement this?
I managed to find simple solution for my use case, without creating refs for comments, passing them, etc. Since my hierarchy of components is like this:
Post --> render component Comments
Comments --> render
multiple components Comment with props data passed from Post
In Post component I created function:
scrollToComment= () => {
let currentLocation = window.location.href;
const hasCommentAnchor = currentLocation.includes("/#");
if (hasCommentAnchor) {
const anchorCommentId = `${currentLocation.substring(currentLocation.indexOf("#") + 1)}`;
const anchorComment = document.getElementById(anchorCommentId);
if(anchorComment){
anchorComment.scrollIntoView({ behavior: "smooth" });
}
}
}
Then I render Comments like this:
<Comments limit={limit} post={post} scrollToComment={this.scrollToComment} />
In Comments I generate comments after some sorting like this:
{sortedComments.map((comment, i) => <Comment key={i} {...comment} scrollToComment={this.props.scrollToComment}/> )}
and finally in Comment component I execute scrollToComment in ComponentDidMount():
if(this.props.scrollToComment)
this.props.scrollToComment(this.props._id);
After that when I go to some URL I get nice smooth scrolling to the comment specified in hash part of URL.
I tried #Christopher solution but it didn't worked for me.
I really liked your solution #SaltyTeemooo. Inspired by it I found an even simpler way without any callbacks.
My setup is very similar so lets say I am dealing with posts and comments.
In Post I create the Comments (simpified) like this and pass the anchorId:
<Comments anchorId={window.location.href.slice(window.location.href.indexOf("#") + 1)} props... />
In Comments I pass the anchor id along into Comment.js
<Comment anchorId={props.anchorId} props.../>
And then in the Comment, I scroll the current element into view, if it is the linked one
import React, { useRef, useEffect } from 'react';
function Comment () {
const comment = useRef(null); //to be able to access the current one
useEffect(() => {
if(props.anchorId === props.commentData.id)
{
comment.current.scrollIntoView({ behavior: "smooth" });
}
}, []) //same as ComponentDidMount
return(
<div id={props.commentData.id} ref={comment}> //here is where the ref gets set
...
</div>
)
}
Took a pretty solid amount of time but try this sandbox: https://codesandbox.io/s/scrollintoview-with-refs-and-redux-b881s
This will give you a ton of insight on how to scroll to an element using a URL param.
import React from "react";
import { connect } from "react-redux";
import { getPost } from "./postActions";
class Post extends React.Component {
constructor(props) {
super(props);
this.state = {
activeComment: null
};
this._nodes = new Map();
}
componentDidMount() {
this.props.getPost(this.props.match.params.id);
const path = window.location.href;
const commentId = path.slice(path.indexOf("#") + 1);
if (commentId) {
this.setState({
activeComment: commentId
});
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.activeComment !== prevState.activeComment) {
this.scrollToComment();
}
}
scrollToComment = () => {
const { activeComment } = this.state;
const { comments } = this.props.posts.post;
const nodes = [];
//Array.from creates a new shallow-copy of an array from an array-like or iterable object
Array.from(this._nodes.values()) //this._nodes.values() returns an iterable-object populated with the Map object values
.filter(node => node != null)
.forEach(node => {
nodes.push(node);
});
const commentIndex = comments.findIndex(
comment => comment.id == activeComment
);
if (nodes[commentIndex]) {
window.scrollTo({
behavior: "smooth",
top: nodes[commentIndex].offsetTop
});
}
};
createCommentList = () => {
const { post } = this.props.posts;
const { activeComment } = this.state;
if (post) {
return post.comments.map((comment, index) => {
return (
<div
key={comment.id}
className={
"comment " + (activeComment == comment.id ? "activeComment" : "")
}
ref={c => this._nodes.set(comment.id, c)}
>
{comment.text}
</div>
);
});
}
};
displayPost = () => {
const { post } = this.props.posts;
if (post) {
return (
<div className="post">
<h4>{post.title}</h4>
<p>{post.text}</p>
</div>
);
}
};
render() {
return (
<div>
<div>{this.displayPost()}</div>
<div>{this.createCommentList()}</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
posts: state.posts
};
};
const mapDispatchToProps = dispatch => {
return {
getPost: postId => {
dispatch(getPost(postId));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Post);
In my simple case where there is no async content loading, I got the desired scrolling behavior by just adding this at the top of the page:
useEffect(() => {
const href = window.location.href
if (href.includes("#")) {
const id = `${href.substring(href.indexOf("#") + 1)}`
const anchor = document.getElementById(id)
if(anchor){
anchor.scrollIntoView({ behavior: "smooth" })
}
}
}, [])
FYI, this was for some FAQ pages consisting of a bunch of FaqEntry objects, each with a question and answer. The code below allows linking to individual entries that initialize with the answer open.
export default function FaqEntry({title, history, children}) {
if(!history) console.log("OOPS, you forgot to pass history prop", title)
const createName = title => title.toLowerCase().replace(/[^\sa-z]/g, "").replace(/\s\s*/g, "_")
const id = createName(title)
const href = window.location.href
const isCurrent = href.includes("#") && href.substring(href.indexOf("#") + 1) === id
const [open, setOpen] = useState(isCurrent)
function handleClick() {
setOpen(!open)
if (history && !open) {
const pathname = window.location.pathname + "#" + id
history.replace(pathname)
}
}
return <div id={id} className={`faqEntry ${open ? "open" : "closed"}`}>
<div className="question" onClick={handleClick}>{title}</div>
<div className="answer">{children}</div>
</div>
}
I pass the history object from React Router so that I can update the browser history without triggering a page reload.
Mensure...
import React, { useEffect } from 'react';
const MainApp = () => {
const MyRef = React.createRef();
useEffect(() => { // Same like ComponentDidMount().
scrollTo();
})
const scrollTo = () => {
window.scrollTo({
top:myRef.offsetTop,
behavior: "smooth" // smooth scroll.
});
}
return (
<div ref={MyRef}>My DIV to scroll to.</div>
)
}

How to trigger INPUT FILE event REACTJS by another DOM

I have a INPUT BUTTON and INPUT FILE, I want to click the BUTTON and it will trigger the INPUT FILE event in REACT JS.
React.createElement('input',{type:'file', name:'myfile'})
then the button
React.createElement('a',{onClick: this.doClick},'Select File')
So how to define and trigger the INPUT FILE click event when we click the A HREF?
Your help is appreciate.
:-)
Update: Sep 18, 2021
Note: On NextJS, I was facing onChange event is not trigged from input file element. For that, we can use onInputCapture or onChangeCapture. For more detailed information, Stackoverflow - onChange event is not firing
Basic example on onChangeCapture as per our requirement. Requires React ^16.8,
const Dummy = () => {
const inputFileRef = React.useRef();
const onFileChangeCapture = ( e: React.ChangeEvent<HTMLInputElement> ) {
/*Selected files data can be collected here.*/
console.log(e.target.files);
};
const onBtnClick = () => {
/*Collecting node-element and performing click*/
inputFileRef.current.click();
};
return (
<form>
<input
type="file"
ref={inputFileRef}
onChangeCapture={onFileChangeCapture}
/>
<button onClick={onBtnClick}>Select file</button>
</form>
);
};
Using useRef Hook in functional components. Requires React ^16.8,
const Dummy = () => {
const inputFileRef = useRef( null );
const onFilechange = ( e ) => {
/*Selected files data can be collected here.*/
console.log( e.target.files );
}
const onBtnClick = () => {
/*Collecting node-element and performing click*/
inputFileRef.current.click();
}
return (
<form className="some-container">
<input
type="file"
ref={inputFileRef}
onChange={onFileChange}
/>
<button onClick={onBtnClick}>Select file</button>
</form>
)
}
Class Implementation with React.createRef() and handling click with node element.
class Dummy extends React.Component {
constructor( props ) {
super( props );
this.inputFileRef = React.createRef();
this.onFileChange = this.handleFileChange.bind( this );
this.onBtnClick = this.handleBtnClick.bind( this );
}
handleFileChange( e ) {
/*Selected files data can be collected here.*/
console.log( e.target.files );
}
handleBtnClick() {
/*Collecting node-element and performing click*/
this.inputFileRef.current.click();
}
render() {
return (
<form className="some-container">
<input
type="file"
ref={this.inputFileRef}
onChange={this.onFileChange}
/>
<button onClick={this.onBtnClick}>Select file</button>
</form>
)
}
}
You don't need jQuery for this. You don't even need an event handler. HTML has a specific element for this, called label.
First, make sure your input element has an id attribute:
React.createElement('input',{type:'file', name:'myfile', id:'myfile'})
Then, instead of:
React.createElement('a',{onClick: this.doClick},'Select File')
Try:
React.createElement('label',{htmlFor: 'myfile'},'Select File')
(Instead of adding htmlFor and id attributes, another solution is to make the input element a child of the label.)
Now clicking the label should trigger the same behaviour as clicking the input itself.
You could trigger the input type file with ref, f.e:
on your class component:
<input
ref={fileInput => this.fileInput = fileInput}
type="file"
/>
<button onClick={this.triggerInputFile}> Select File </button>
and make a function on that class component too:
triggerInputFile = () => this.fileInput.click()
Using Hooks with useref:
import React, {useRef} from 'react';
const FancyInput = () => {
const fileInput = useRef(null)
const handleClick = () => {
fileInput.current.click()
}
const handleFileChange = event => {
console.log("Make something")
}
return(
<div className="patientactions-container">
<input
type="file"
onChange={(e) => handleFileChange(e)}
ref={fileInput}
/>
<div onClick={() => handleClick()}></div>
</div>
)
}
export default FancyInput;
Building on the answer from #YÒGÎ , here is an implementation using TypeScript:
class Dummy extends React.Component {
fileInputRef: React.RefObject<HTMLInputElement> = React.createRef();
forwardClickToInputElement = () => {
this.fileInputRef.current!.click();
};
handleUploadDemand = (ie: ChangeEvent<HTMLInputElement>) => {
const fileList: FileList = ie.target.files;
// do something with the FileList, for example:
const fileReader = new FileReader();
fileReader.onload = () => {
const str = String(fileReader.result);
try {
const parsedContent = YOUR_OWN_PARSING(str);
} catch (error) {
// YOUR OWN ERROR HANDLING
}
};
fileReader.readAsBinaryString(fileList[0])
}
render() {
return (
<div className="some-container">
<button onClick={this.forwardClickToInputElement}>Select File</button>
<input ref={this.fileInputRef} type="file" onChange={this.handleSelectFile} hidden={true}/>
</div>
)
}
}
References:
Solution for how to use refs in React with Typescript https://stackoverflow.com/a/50505931/2848676
Use ! operator for ref type narrowing https://medium.com/#martin_hotell/react-refs-with-typescript-a32d56c4d315
const CustomInput = () => {
const handleClick = () => {
document.getElementById("file_upload").click();
};
const handleFileChange = (event) => {
console.log("Make something");
};
return (
<div className="patientactions-container">
<input type="file" id="file_upload" onChange={(e) => handleFileChange(e)} />
<div onClick={() => handleClick()}></div>
</div>
);
};
export default CustomInput;
EDIT: This is a question I answered a long time ago not knowing very much react at this time. The fun thing is that it has been considered valid ^^.
So for anyone reading this answer; this answer is wrong and is a very good example of something you shouldn't do in react.
Please find below a nice anti-pattern, again, don't do it.
=================================================
You can achieve this using jQuery:
this.doClick: function() {
$('input[type=file]').trigger('click');
}
React does not provide specific functions to trigger events, you can use jQuery or simply native Javascript: see Creating and triggering events on MDN

Categories