How do you focus and select an input when it loads? - javascript

I was unable to find good answer to the question of focusing and selecting an input element on load; initially I just used a useRef(), set it as the ref and then on component load (useEffect(,[])) I would ref.current.focus() / ref.current.select(). But input's built in autoFocus and onFocus are much simpler solutions.
In other words, consider the following code snippet where we want to focus on the input when you press the button (it doesn't work here)
// Get hook functions (specific for use with stackoverflow code snippet)
const { useState } = React;
const Example = ({title}) => {
// hook that keeps track of the editing state
const [editing, setEditing] = useState(false);
// switch to input when button is pressed
const InputComponent = () => {
if (!editing) {
return ( <span>click for input</span>)
}
else {
return ( <input /> )
}
}
return (
<div>
<p>{title}</p>
<p>{InputComponent()}</p>
<button onClick={() => setEditing(!editing)}>
Click me
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="The goal is to have the input automatically focused" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Now, if we use references
const { useState, useRef, useEffect } = React;
const Example = ({title}) => {
const [editing, setEditing] = useState(false);
// useRef for a reference to the input
const inputRef = useRef();
const InputComponent = () => {
if (!editing) {
return ( <span>click for input</span>)
}
else {
// set the ref as a reference to the input
return ( <input ref={inputRef}/> )
}
}
// when editing updates, run this code
useEffect(() => {
// when editing is true, focus the input
if (editing) {inputRef.current.focus()}
}, [editing])
return (
<div>
<p>{title}</p>
<p>{InputComponent()}</p>
<button onClick={() => setEditing(!editing)}>
Click me
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="The goal is to have the input automatically focused" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
further if you want to select the value of the input when it's focused you can do the following:
const { useState, useRef, useEffect } = React;
const Example = ({title}) => {
const [editing, setEditing] = useState(false);
// define a state variable for the input value
const [inputValue, setValue] = useState("value of the input");
const inputRef = useRef();
const InputComponent = () => {
if (!editing) {
return ( <span>click for input</span>)
}
else {
return (
<input
ref={inputRef}
// define the value of the input
value={inputValue}
// when the input is changed, update the state variable
onChange={(event) => setValue(event.target.value)}
/>
)
}
}
useEffect(() => {
if (editing) {
inputRef.current.focus();
// focus and select the value of the input
inputRef.current.select();
}
}, [editing])
return (
<div>
<p>{title}</p>
<p>{InputComponent()}</p>
<button onClick={() => setEditing(!editing)}>
Click me
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="The goal is to have the input automatically focused" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
This methodology would allow you to set some base value of the input (like a stylized way of representing the value, and then when the user clicks that stylize value it begins editing).
This solution is well and good, but it's bulky and requires that you pass references down to the component in some cases, and is just generally not clean. So is there a better solution? yes....

Here's a simplified version that will accomplish the same effect but much simpler
const { useState, useRef, useEffect } = React;
const Example = ({title}) => {
const [editing, setEditing] = useState(false);
const [inputValue, setValue] = useState("value of the input");
// no need for a reference
const InputComponent = () => {
if (!editing) {
return ( <span>click for input</span>)
}
else {
return (
<input
// don't need to set an input reference
onChange={(event) => setValue(event.target.value)}
value={inputValue}
onFocus={(event) => event.target.select()} // selects the value on load
autoFocus // focuses on load
/>
)
}
}
// no need for a useEffect
return (
<div>
<p>{title}</p>
<p>{InputComponent()}</p>
<button onClick={() => setEditing(!editing)}>
Click me
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="The goal is to have the input automatically focused" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
There you go, a much faster, simpler, and easier to understand implementation of focusing on an input on load. Cheers! :)

Related

State becomes empty

Counter File
import React, { useState } from "react";
import Widget from "./widget";
const Counter = () => {
const [form, setForm] = useState(<></>)
const [text, setText] = useState("")
const onCounterChange =() => {
setText(text)
}
const formLoad =() =>{
setForm(
<Widget
onCounterChange={onCounterChange}
children={
<input type="text" onChange={(e) =>{
setText(e.target.value)
}}/>
}
/>
)
}
return (
<div>
{text}
<button onClick={formLoad}>
load widget
</button>
{form}
</div>
)
}
export default Counter
Widget File
import React from 'react'
export default function Widget(props) {
return (
<div className="buttons">
{props.children}
<button onClick={props.onCounterChange}>Save</button>
</div>
)
}
I have created small text printing page . for some purpose I have added children in a diff component and handling widget in a state , so when I try to change the data , text state is changing but when I click save text state becomes empty
As I mentioned in my comment putting a component in state probably isn't the best way of approaching this. Instead I would have a boolean state that allows you to toggle the component on/off.
const { useState } = React;
function Example() {
const [ showWidget, setShowWidget ] = useState(false);
const [ text, setText ] = useState('');
function handleChange(e) {
setText(e.target.value);
}
function handleClick() {
setShowWidget(!showWidget);
}
function handleSave() {
console.log(`Saved state: ${text}`);
}
return (
<div>
<p className="text">Current state: {text}</p>
<button onClick={handleClick}>
Load widget
</button>
{showWidget && (
<Widget>
<input
type="text"
onChange={handleChange}
/>
<button onClick={handleSave}>Save</button>
</Widget>
)}
</div>
);
}
function Widget({ children }) {
return <div className="widget">{children}</div>;
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.widget { margin-top: 1em; }
.text { margin-bottom: 1em; }
<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="react"></div>

Click button once click just render text

How to create a button than once click the button will render just text by using react hook
const WelcomeButton = (props) => {
const[welcomeBtn, setwelcomeBtn] = useState()
const handelClick = () => {
if() {
retun <p>Hi John</p>
}
}
return (
<div>
<button onClick={handelClick}> Welcome </button>
</div>
)
;
};
I suggest using a state that tracks that the button has been clicked, and update/toggle this value in the handleClick callback. Conditionally render either the button or the welcome text based on this state.
const WelcomeButton = (props) => {
// initial state true to show button
const [welcomeBtn, setWelcomeBtn] = React.useState(true);
const handelClick = () => {
// toggle false to hide button and display welcome message
setWelcomeBtn(false);
};
return (
<div>
{welcomeBtn ? (
<button onClick={handelClick}> Welcome </button>
) : (
<p>Hi John</p>
)}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(
<WelcomeButton />,
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" />

React: How can I remove a specific div element after clicking its associated button?

I have this component which adds a div and its elements to the dom on button click. The adding part works fine as expected but the issue arises when I want to delete.
Right now when I click on the delete button, it does remove the item but it doesn't remove that specific item which the button is associated with. It just removes the div from the top or bottom.
I have been trying to remove that specific div whose button has been clicked to remove. How can I achieve that?
Here's the CodeSandbox.
And here's the code:
import { useState } from "react";
const App = () => {
const [ counter, setCounter ] = useState( 1 );
const handleAddDiv = () => {
setCounter( counter + 1 );
};
const handleRemoveDiv = () => {
setCounter( counter - 1 );
};
return (
<div className="App">
{
Array.from(Array( counter )).map(( item, idx ) => (
<div>
<div>
<input type="text" />
<button onClick={handleRemoveDiv}>Remove</button>
</div>
</div>
))
}
<button onClick={handleAddDiv}>Add</button>
</div>
);
}
export default App;
This is not prefered react way of doing things, but this will work:
import "./styles.css";
import { useState } from "react";
const App = () => {
const [counter, setCounter] = useState(1);
const handleAddDiv = () => {
setCounter(counter + 1);
};
const removeNode = (idx) => document.getElementById(`id-${idx}`).remove();
return (
<div className="App">
{Array.from(Array(counter)).map((item, idx) => (
<div key={idx} id={`id-${idx}`}>
<div>
<input type="text" />
<button onClick={() => removeNode(idx)}>Remove</button>
</div>
</div>
))}
<button onClick={handleAddDiv}>Add</button>
</div>
);
};
export default App;
Generaly if you would like to have it made correactly then you would want to map on a real array and have every item in array eighter having an unique id or simply use map index and then based on which item you click write a function to remove from that array our specific element.
Map over an array of unique Ids
First of all, you should map over an array of items instead of an integer value.
So, on click of add button, you should push a unique ID to the array of items where each ID would denote an item being rendered in your app.
Now, when you click on remove button, you would need to remove that ID from the array of items, which would result in "deletion" of that div from the app.
In my case, I have considered timestamp as a unique ID but should explore other options for generating unique IDs. Working with indices is anti pattern in React especially when you are mapping over an array in JSX as you would encounter issues at one point of time. So, it's a good idea to maintain unique Ids.
Note: Damian's solution is not ideal as DOM Manipulation is avoided in React.
const { useState, useCallback } = React;
const Item = ({ id, removeDiv }) => {
const clickHandler = useCallback(() => {
removeDiv(id);
}, [id, removeDiv]);
return (
<div>
<input type="text" />
<button onClick={clickHandler}>Remove</button>
</div>
);
};
const App = () => {
const [items, setItems] = useState([]);
const addDiv = useCallback(() => {
// using timestamp as a unique ID
setItems([...items, new Date().getTime()]);
}, [items]);
const removeDiv = useCallback((itemId) => {
// filter out the div which matches the ID
setItems(items.filter((id) => id !== itemId));
}, [items]);
return (
<div className="app">
{items.map((id) => (
<Item key={id} id={id} removeDiv={removeDiv} />
))}
<button onClick={addDiv}>Add</button>
</div>
);
};
ReactDOM.render(<App />,document.getElementById("react"));
.app {
text-align: center;
font-family: sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I would use an array state instead of a counter state, because otherwise you don't know which element has to be removed.
import { useState } from "react";
import "./styles.css";
let counter = 1;
export default function App() {
const [array, setArray] = useState([0]);
const handleAddDiv = () => {
setArray((prev) => [...prev, counter++]);
};
const handleRemoveDiv = (idx) => {
var arrayCopy = [...array];
arrayCopy.splice(idx, 1);//remove the the item at the specific index
setArray(arrayCopy);
};
return (
<div className="App">
{array.map((item, idx) => (
<div key={item}>
<div>
<input type="text" />
<button onClick={()=>handleRemoveDiv(idx)}
>Remove</button>
</div>
</div>
))}
<button onClick={handleAddDiv}>Add</button>
</div>
);
}
When I am adding a new item, I give it the value counter++, because I will use it as a key, and a key should be unique.

React useref not adding focus state to field

I've been trying to find a solution for this, where I'd like to add the focus state to my input field. But it doesn't seem to be working:
const textareaRef = useRef<HTMLInputElement>(null);
...
const handleClick = () => {
if (textareaRef.current) {
alert('Field clicked');
textareaRef.current.focus();
textareaRef.current.click();
}
};
...
<input
ref={textareaRef}
onClick={handleClick}
onFocus={(e) => e.persist()}
spellCheck="false"
/>
This doesn't work, and the main reason is to show the mobile keyboard. See video here of it not working https://share.getcloudapp.com/jkuYLqO5 and here without alert https://share.getcloudapp.com/wbuKwrLE
I cannot reproduce the bug on desktop browser. What browser is it in the screen recording?
function App() {
const textareaRef = React.useRef();
const handleClick = () => {
if (textareaRef.current) {
console.log('Field clicked');
textareaRef.current.focus();
}
};
return (
<div>
<div onClick={handleClick}>click me to focus input</div>
<input
ref={textareaRef}
onFocus={(e) => e.persist()}
/>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://unpkg.com/react#16.14.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.14.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can do this in useEffect() Hook. Below component mocks your scenario. As the component loads, I make focus() appear on input.
import React, { useEffect, useRef } from 'react';
const UseRefBasics = () => {
const refContainer = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log(refContainer.current.value);
};
useEffect(() => {
console.log(refContainer.current);
refContainer.current.focus();
});
return (
<>
<form className='form' onSubmit={handleSubmit}>
<div>
<input type='text' ref={refContainer} />
</div>
<button type='submit'>submit</button>
</form>
</>
);
};
export default UseRefBasics;
This is a general scenario that I have given. You may use useEffect() and get your focus when the state updates by providing another argument to useEffect(()=>{},[state1]) or in whatever way you need it.

React Higher order components, that modifies the input element of wrapped component in it

I have a higher order component like this.
import React from 'react';
const NewComponent = ( WrappedComponent ) => {
class UpdatedComponent extends React.Component {
render() {
// Custom Hook
// const values = useCustomHook(InitialState);
return(
<WrappedComponent />
)
}
}
return UpdatedComponent;
};
export { NewComponent };
And the wrapped component like these.
const App = () => {
return(
<Form>
<input
type = 'text'
placeholder = 'Enter your name' />
<input
type = 'email'
placeholder = 'Enter your email' />
<button
type = 'submit'>
Submit
</button>
</Form>
)
}
The thing is i want to iterate through input elements in the wrapped components and construct a compound state, which i will pass a an Argument to the custom hook in the hoc? Is there a way to achieve this functionality?
I think you should not parse jsx and create data from that but you have not really demonstrated why you need to do this and maybe have a valid use case for it.
Here is how you could parse jsx (the html created from it):
const useCustomHook = (state) => {
if (state !== null) {
console.log('custom hook, state is:', state);
}
};
const Form = ({ children }) => {
const [state, setState] = React.useState(null);
const ref = React.useRef();
useCustomHook(state);
React.useEffect(() => {
debugger;
setState(
Array.from(ref.current.querySelectorAll('input')).map(
({ type, placeholder }) => ({
type,
placeholder,
})
)
);
}, []);
return <div ref={ref}>{children}</div>;
};
const App = () => {
return (
<Form>
<input type="text" placeholder="Enter your name" />
<input type="email" placeholder="Enter your email" />
<button type="submit">Submit</button>
</Form>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories

Resources