this is the solution using refs
import React, { useState, useRef } from "react";
function Bar() {
const ref = useRef();
return (
<div
ref={ref}
onClick={(ev) => {
if (ev.target == ref.current) console.log("Target hit");
}}
>
Click me, maybe <div>test</div>
</div>
);
}
In the above solution, target is not hit when we click on test. Can we
get the same output without using refs
Use the currentTarget property of the event, which will always refer to the element it is registered on, as opposed to target which is the element that triggered it:
function Bar() {
return (
<div
onClick={(ev) => {
if (ev.target === ev.currentTarget) console.log("Target hit");
}}
>
Click me, maybe <div>test</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>
);
}
When I press on the that element I want to log it's attributes(value). I think, I am not using useRef hook correctly.
Link:
https://codesandbox.io/s/access-dom-element-forked-lphg6?from-embed=&file=/src/App.js:0-405
import "./styles.css";
import { useRef } from "react";
export default function AccessingElement() {
const elementRef = useRef();
const fake_data = ["hello", "bye", "yes", "no"];
return (
<div>
{fake_data.map((item, idx) => (
<div value={item} ref={elementRef} key={idx} onClick={() => console.log(elementRef.current)}>
{item}
</div>
))}
</div>
);
}
The issue is being seen because all the items are being assigned to the same ref. Hence, on clicking any element, text corresponding to the last assigned item (i.e no) gets logged.
In this case, an array of refs needs to be maintained, such that each ref in the array corresponds to an item. Something like this :
import "./styles.css";
import { useRef } from "react";
export default function AccessingElement() {
const elementRef = useRef([]);
const fake_data = ["hello", "bye", "yes", "no"];
return (
<div>
{fake_data.map((item, idx) => (
<div
ref={el => elementRef.current[idx] = el}
key={idx}
onClick={() => console.log(elementRef.current[idx])}>
{item}
</div>
))}
</div>
);
}
Here is the working CodeSandbox Link
You can not set value for the div. If you want to set some data for the div, you can do like this.
<div data-val={item} ref={elementRef} key={idx}
onClick={() => console.log(elementRef.current.dataset.val)}>
{item}
</div>
If you want to get the text inside the div, you just need to use textContent
elementRef.current.textContent
If I have a component like
const Comp = ({children}) => {
//some code
return (
<div>
{children}
</div>
)
}
and then call it like
<Comp>
<input onChange={...} />
<input onChange={...} />
</Comp>
How can I change the focus to the first input field of this component when the component renders, from within the Comp component.
Ideally I would like to have a useEffect function or something which looks something like
useEffect(() => {
thisComponent.firstChild.focus()
})
You need two things, useRef and useEffect, useRef for getting target element ref, and useEffect for handling focusing when then component renders.
children in a component props is an array, so you can manipulate it, you can use index to get which child element you want to set ref, and then call focus() by ref in useEffect:
function App(props) {
const firstChildRef = useRef(null);
useEffect(() => {
if(firstChildRef.current) {
firstChildRef.current.focus()
}
}, [firstChildRef])
return (
<div className="App">
{props.children.map((child, index) => {
if(index === 0) {
return {...child, ref: firstChildRef};
}
return child;
})}
</div>
);
}
import React, { useState } from 'react'
export default function App() {
const [todos , set_todos] = useState([''])
const [input , set_input] = useState('')
const new_todo = (event) =>{
set_todos = ([...todos,input]);
}
return (
<>
<h1>hello world</h1>
let input = <input value={input} onChange={event=> set_input(event.target.value)}/>
<button onClick ={new_todo}>add todo</button>
<ul>
{todos.map(todo =>(
<li>{todo}</li>
))}
</ul>
</>
)
}
the error is in 7th line of the code
i am a totally new beginner so it would be helpful if you explain it to me
If you want to update your state (that is an array) in react hooks, you should try like this :
const new_todo = (input) => set_todos(oldState => [...oldState,input])
with this code, you will not see any error but I have some offer for your code that make it better:
put your inputs such as input and buttons in the form tag
use variable declarations outside of return ( let return be for HTML tag and its better to use your logics outside of return ) make it easier to read
and I think your code can be like this:
import React, { useState } from 'react'
export default function App() {
const [todos , set_todos] = useState([''])
const submitTask = (e) =>{
// Calling preventDefault() during any stage of event flow cancels the event,
// meaning that any default action normally taken by the implementation
// as a result of the event will not occur
e.preventDefault();
const { taskInput } = e.target; // get your input by name
// assign input value to your state
set_todos(oldState => [...oldState, taskInput.value ])
}
return (
<>
<h1>hello world</h1>
<form onSubmit={submitTask}>
<input name="taskInput" />
<button type="submit">add todo</button>
</form>
<ul>
{todos.map(todo =>(
<li>{todo}</li>
))}
</ul>
</>
)
}
You should use set_todos like below:
set_todos([...todos, input]);
Because set_todos is a function. You can find the State Hook's usage in Introducing Hooks.
I am looking to be able to update the size of .swiper-wrapper when there is a DOM event on the current slide. The method swiper.updateSize()/swiper.update() is firing after the DOM event but it is returning undefined. This is odd because when I console.log(swiper) (the instance of swiper) it returns the swiper object instance as expected. I have added the prop autoHeight to so according to the documentation, the height of the .swiper-wrapper should increase/decrease depending on the height of the current slide after the swiper.updateSize()/swiper.update() method is called
Is there something I am doing wrong here?
import React, { useState } from 'react';
function ParentComponent(props) {
let [swiper, setSwiper] = useState(null)
return(
<Swiper
onSwiper={(swiper) => {setSwiper(swiper)}}
autoHeight
>
<ChildComponent
title={"title"}
text={"text"}
swiper={swiper}
/>
</Swiper>
)
}
function ChildComponent(props) {
let [active, setActive] = useState(false)
let swiper = props.swiper
// toggle active class
const handleClick = () => {
if (active) {
setActive(false)
}
else {
setActive(true)
// returns swiper instance
console.log(swiper)
// returns undefined
swiper.updateSize();
}
}
return (
<div className={"infos" + (active ? ' active' : '')}>
<div onClick={handleClick}>
<h4>{props.title}<span></span></h4>
</div>
<div className="text">
<p>{props.text}</p>
</div>
</div>
)
}
export default ChildComponent
ok managed to fix this by setting a timeout and waiting until after the transition had finished (or roughly close to it). The issue seemed to be that the max-height transition hadn't finished by the time the update() method was called on swiper. I also used a different method called .updateAutoHeight() which worked
my code now looks like this:
import React, { useState } from 'react';
function ParentComponent(props) {
let [swiper, setSwiper] = useState(null)
return(
<Swiper
onSwiper={(swiper) => {setSwiper(swiper)}}
autoHeight
>
<ChildComponent
title={"title"}
text={"text"}
swiper={swiper}
/>
</Swiper>
)
}
function ChildComponent(props) {
let [active, setActive] = useState(false)
let swiper = props.swiper
// toggle active class
const handleClick = () => {
if (active) {
setActive(false)
setTimeout(() => {
swiper.updateAutoHeight()
}, 200)
}
else {
setActive(true)
setTimeout(() => {
swiper.updateAutoHeight()
}, 250)
}
}
return (
<div className={"infos" + (active ? ' active' : '')}>
<div onClick={handleClick}>
<h4>{props.title}<span></span></h4>
</div>
<div className="text">
<p>{props.text}</p>
</div>
</div>
)
}
export default ChildComponent
I would suggest changing the timeout ms depending on how long the transition takes to finish. The higher the height of the max-height transition, the longer it will take and therefore the more delay you will need to use on the setTimeout ms for it to be picked up by the JS
All methods of swiper need to be called in setTimeout function