How to focus and select a checkbox using React ref? - reactjs

I have been looking around a method to correctly focus and select a checkbox in React code.
The methods focus() and select() that I'm using in the example below are not working :
import React, { useRef } from "react";
export const HelloWorld = () => {
const checkboxref = useRef(null);
const handleOnClick = () => {
checkboxref.current.focus();
checkboxref.current.select();
};
return (
<div>
<button onClick={handleOnClick}>Focus</button>
<input type="checkbox" ref={checkboxref} />
</div>
);
};
When I click on the button, my checkbox is not focused and not selected...
Any solution please ?
Thank you so much.

You don't need to create a separate function to handle onChange event
const checkboxref = useRef(null);
You can simply get the current value of the checkbox with:
checkboxref.current.checked
// which returns a boolean

use this one it might help you. here I am using createRef instead of useRef and also uses the callback hook which ensures the availability of ref when you click the button.
import React,{createRef, useCallback} from 'react';
export const HelloWorld = () => {
const checkboxref = createRef();
const handleOnClick = useCallback(() => {
const node = checkboxref.current;
if(node){
node.focus();
node.select();
}
}, [checkboxref]);
return (
<div>
<button onClick={handleOnClick}>Focus</button>
<input type="checkbox" ref={checkboxref} />
</div>
);
};

The select methods selects text in elements such as text inputs and text areas, so I'm not sure what effect you expect it to have on the checkbox. As for focus, it can focus, but again, there is not much you can do with a focused checkbox. I can only think of styling it https://jsfiddle.net/4howanL2/1/

Related

React input onChange not rerendering state when useState has default value

So this is working, input changes when I type.
const [editfield, setEdit_field] = React.useState();
function updateField(event) {
setEdit_field(event.target.value);
}
function editPost() {
setPostbody(<div><input onChange={updateField} value={editfield}></input></div>)
}
But when a put a default value in the useState it doesnt work anymore
const [editfield, setEdit_field] = React.useState(post.body);
function updateField(event) {
setEdit_field(event.target.value);
}
function editPost() {
setPostbody(<div><input onChange={updateField} value={editfield}></input></div>)
}
Access code here: https://codesandbox.io/s/brt7ok
You were setting JSX inside the state, that might be the issue, I have created a codesandbox demo which will help you in conditional Rendering
DEMO
You are rendering that function so when state will updated it re-run that function and resetting the value.
You can use below approach.
Take one state which represents the field is editable or not. And add condition with input component that it should only render when field is editable.
For example,
const [isEditable, setIsEditable] = useState(false);
<button onClick={() => setIsEditable(!isEditable)}>edit</button>
isEditable && (
<div>
<input onChange={updateField} value={editfield} />
</div>
)
For more idea, just put console before return. You will get idea.
Hope this helps :)
import React, { useState } from "react";
export default function App() {
let body = "hello";
const [editfield, setEditfield] = useState(body);
const [visible, setVisible] = useState(false);
function updateField(event) {
setEditfield(event.target.value);
}
function editPost() {
setVisible(true);
}
return (
<div>
<div>{visible?<div>
<input onChange={updateField} value={editfield}/>
</div>:body}</div>
<div>
<button onClick={editPost}>edit</button>
</div>
</div>
);
}

Using react useRef() --why my ref is null?

I have a simple example
function Node() {
const [hidden, setHidden] = useState(true);
const inputRef = useRef(null)
console.log(inputRef);
return (
<div>
{!hidden && <h2 ref={inputRef}>Hello World</h2>}
{hidden && <button onClick={() => setHidden(false)}>Show Child</button>}
</div>
)
}
Upon clicking the button, I would expect that the h2 DOM element is attached to my ref. However, I found that the ref.current is still null upon logging, but if I expand the object, it contains the DOM node.
How am I supposed to access the DOM element via my ref? At the time I want to reference it, for example inputRef.current.getBoundingClientRect(), it's always shown as null.
Your help is much appreciated!
You are trying to use the ref in the render phase, but the ref will be populate once React paint the screen, in the commit phase.
So, call it in a useEffect or useLayoutEffect
// run synchronously after all DOM mutations
React.useLayoutEffect(() => {
// do something with inputRef
}, [])
Your ref is not initialize when setHidden set to false if you want to use it you need some interactions like this
import React, { useRef, useState, useEffect } from 'react';
function App() {
const [hidden, setHidden] = useState(true);
const inputRef = useRef(null)
console.log(inputRef);
const handleChange=()=>{
console.log(inputRef);
}
useEffect(()=>{
console.log(inputRef);
},[hidden])
return (
<div>
<input onChange ={handleChange}></input>
{!hidden && <h2 ref={inputRef}>Hello World</h2>}
{hidden && <button onClick={() => setHidden(false)}>Show Child</button>}
</div>
)
}
export default App;

React - two buttons - a click on one of them opens both

I have a React button component with onClick handler and its state - stating whether it was opened on click. If I render two of such components in a wrapper container, and I click on one of them, both buttons update the state. How should I handle the state, so that only one of the buttons updates without using ids?
import React, {useState} from 'react';
const Button = (props) => {
const [open, setOpen] = useState(false);
const text = open ? 'Open' : 'Closed';
const toggleButton = () => { setOpen(!open) };
return (
<button onClick={toggleButton}>{text}</button>
)
}
// parent component
import React from 'react';
import Button from './Button'
const ButtonsWrapper = () => {
return (
<div>
<Button />
<Button />
</div>
)
}
I also tried reversing the logic and putting the state in a wrapper component, and then passing the onClick handler as a props to a button, but the sitation is the same. They both change the state at the same time when one of them is clicked.
I am using React Hooks.
My understanding is that you are saying that when you click one button both buttons seems to have their state updated, but you only want the clicked button to update its state.
(i.e. if you click Button A only that button will show 'Open' as its text, Button B will continue to show closed)
If the above is right, then your code should already do the correct thing. If it doesn't then you might have a bug elsewhere that would cause this.
If however you want to click one button and BOTH should switch state then you could achieve this by keeping track of the state in the parent component:
import React, {useState} from 'react';
const Button = (props) => {
const text = props.isOpen ? 'Open' : 'Closed';
const handleClick = () => {
// do some stuff you want each button to do individually
.....
props.onClick()
}
return (
<button onClick={handleClick}>{text}</button>
)
}
// parent component
import React from 'react';
import Button from './Button'
const ButtonsWrapper = () => {
const [buttonState, setButtonState] = useState(false)
return (
<div>
<Button onClick={() => setButtonState(!buttonState)} isOpen={buttonState} />
<Button onClick={() => setButtonState(!buttonState)} isOpen={buttonState} />
</div>
)
}

Radio button onChange event not firing if a new element is added

Hope you are well, I stumbled upon an issue that I do not understand and before looking any further into the (pretty convoluted) code I inherited, I want to check with you if this is an expected behaviour.
{Object.keys(locations).map((keyName) => {
const { name: territoryName, code: territoryCode } = locations[keyName];
return (
<RadioButton
key={`LGRB${territoryName}`}
style={{ margin: '10px 0' }}
onChange={() => console.log('onChange')}
checked={territoryCode === selectedTerritoryCode}
name={territoryName}
label={territoryName}
dark
rightAlign
/>
);
})}
All works fine and as expected, it renders a series of radio buttons with selectable locations, onChange triggers as expected.
If I now push a new element to locations, it correctly renders the new element but:
First time a user clicks on any of the radio buttons, the onChange event doesn't trigger
Second time a user clicks on any of the radio buttons, the onChange event now triggers
Why?
Thank you for any help
Edit:
This is the <RadioButton> component:
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { RadioButton as GrommetRadioButton } from 'grommet';
const RadioButtonWrapper = styled.div`
StyledComponentsStyles omitted
`;
const RadioButton = ({ dark, rightAlign, ...props }) => (
<RadioButtonWrapper dark={dark} rightAlign={rightAlign}>
<GrommetRadioButton {...props} />
</RadioButtonWrapper>
);
RadioButton.propTypes = {
dark: PropTypes.bool,
rightAlign: PropTypes.bool,
};
export default RadioButton;
I think there is an issue with how the state of the checked RadioButton is being handled. To remove the clutter of handling this state and these strange side-effects coming from the rendering of individual RadioButton, try to see if RadioButtonGroup is solving your problem, it will be more straightforward to control the RadioButton(s) input events and state management.
Just for a POC, here is an example code that uses RadioButtonGroup, with dynamically added RadioButton items that react (pun intended) as expected to onChange events.
import React, { useState } from "react";
import { render } from "react-dom";
import { grommet, Box, Grommet, RadioButtonGroup } from "grommet";
export const App = () => {
const [fruits, setFruits] = useState(["pear", "banana", "mango"]);
const [value, setValue] = useState(fruits[0]);
const [counter, setCounter] = useState(0);
const handleOnChange = (event) => {
console.log(event.target.value);
setValue(event.target.value);
setFruits([...fruits, "apple" + counter]);
setCounter(counter + 1);
};
return (
<Grommet theme={grommet}>
<Box pad="small">
<RadioButtonGroup
name="radio"
options={fruits}
value={value}
onChange={(event) => handleOnChange(event)}
/>
</Box>
</Grommet>
);
};
render(<App />, document.getElementById("root"));
I'm not sure if that is the answer you were hoping for, but consider it as best practice advice in case you are planning to refactor the current behavior. Good luck!

UseEffect and useCallback still causes infinite loop in react project

I can't seem to resolve an infinite loop issue in my react project.
I'm working on a daily-log react app. Let me explain the project briefly. Here is the picture of the code for quick view:
The same code is available at the bottom.
The structure (from top to bottom)
The DailyLog component has a form that uses Question components into which props are passed.
The Question component uses the props to display a question and description. It also contains an Input component into which props are further passed down.
The Input component takes the props and renders the appropriate form input field.
The logic (from bottom to top)
The Input component handles it's own inputState. The state is changed when the user inputs something and the onChangeHandler is triggered.
The Input component also has a useEffect() hook that calls an onInput() function that was passed down as props from DailyLog.
The onInputHandler() in the DailyLog component updates the formState which is the form-wide state containing all input field values. The formState is amended depending on which input field is filled at the time.
The onInputHandler() uses the useCallback() hook which is supposed to stop an infinite loop caused by any parent/child re-renders. But it doesn't work :frowning:
What's wrong in the code? What am I missing here? Code provided below:
//DailyLog.js
import React, { useState, useCallback } from 'react';
import Question from '../components/FormElements/Question';
import questionData from '../components/DailyLog/questionData';
import './DailyLog.css';
const DailyLog = () => {
const [formState, setFormState] = useState();
const onInputHandler = useCallback(
(inputId, inputValue) => {
setFormState({
...formState,
[inputId]: inputValue,
});
},
[formState]
);
return (
<main className="container">
<form action="" className="form">
<Question
id="title"
element="input"
type="text"
placeholder="Day, date, calendar scheme"
onInput={onInputHandler}
/>
<Question
id="focus"
question={questionData.focusQuestion}
description={questionData.focusDescription}
element="textarea"
placeholder="This month's focus is... This week's focus is..."
onInput={onInputHandler}
/>
</form>
</main>
);
};
export default DailyLog;
//Question.js
import React from 'react';
import Input from './Input';
import './Question.css';
const Question = props => {
return (
<div className="form__group">
{props.question && (
<label className="form__label">
<h2>{props.question}</h2>
</label>
)}
<small className="form__description">{props.description}</small>
<Input
id={props.id}
element={props.element}
type={props.type}
placeholder={props.placeholder}
onInput={props.onInput}
/>
</div>
);
};
export default Question;
//Input.js
import React, { useState, useEffect } from 'react';
import './Input.css';
const Input = props => {
const [inputState, setInputState] = useState();
const { id, onInput } = props;
useEffect(() => {
onInput(id, inputState);
}, [id, onInput, inputState]);
const onChangeHandler = event => {
setInputState(event.target.value);
};
// check if question element type is for input or textarea
const element =
props.element === 'input' ? (
<input
id={props.id}
className="form__field"
type={props.type}
value={inputState}
placeholder={props.placeholder}
onChange={onChangeHandler}
/>
) : (
<textarea
id={props.id}
className="form__field"
rows="1"
value={inputState}
placeholder={props.placeholder}
onChange={onChangeHandler}
/>
);
return <>{element}</>;
};
export default Input;
Remove id and onInput from useEffect sensivity list
useEffect(() => {
onInput(id, inputState);
}, [inputState]);
And set default value of inputState to '' as follow:
const [inputState, setInputState] = useState('');
To prevent 'A component is changing an uncontrolled input of type text to be controlled error in ReactJS'. Also you can init formState:
const [formState, setFormState] = useState({title:'', focus:''});

Resources