I have a component that consists of an array of SVG arrows, like this
const App = () => {
const [arrows, createArrow]=useState([<Arrow x="7" y="100"/>,
<Arrow x="50" y="100"/>])
return (
<div>
<button onClick={()=>createArrow([...arrows, <Arrow x="75" y="100"/>])}>Add Force</button>
<ArrowForm newArrow = {createArrow} arrows = {arrows}/>
<svg
style={{
border: '1px solid green',
height: '200px',
width: '100%',
}}
>
<Grid/>
{arrows.map((child) => child)}
</svg>
</div>
)
}
I'd like to set the position of the arrow using a form component, which I've created like this:
export default function ArrowForm({newArrow, arrows}) {
const[position, setPosition] = useState({x:1, y:0})
return(
<div>
<label>x:
<input type="number" value = {position.x} onChange={e=>setPosition(e.target.value)}/>
</label>
<label>y:
<input type="number" value = {position.y} onChange={e=>setPosition(e.target.value)}/>
</label>
<input type="submit" value="Submit" onSubmit= ()=> {newArrow([...arrows, <Arrow x="75" y="100"/>])} />
</div>
)
}
I think I need to pass the arrows state and createArrow function from the App form so that I can create arrows when I press the submit button in the form, but I'm getting a Maximum Update Depth exceeded error.
The bug is in your ArrowForm
<input type="submit"
value="Submit"
onSubmit= {newArrow([...arrows, <Arrow x="75" y="100"/>])} />
it means as soon as your component render call onSubmit instead you have to do this:
<input
type="submit"
value="Submit"
onSubmit={() => newArrow([...arrows, <div x="75" y="100" />])}
/>
Here is the demo. See console. As I don't know what is your arrow component do.
#Shubham Verma answer is correct, but you and he/she missed that input doesn't have onSubmit property, it is form that does.
So you should use onClick.
Here is the sandbox - https://codesandbox.io/s/restless-surf-xb1h1
Related
I am trying to do something like this.
When user Click on Edit Button then Form fields will appear and user can edit the data. otherwise user can only view the data.
I facing the trouble, when user don't want to edit the form and click on cancel button then page start rendering as many times as the page have total form fields.
On my original form I have 80+ form fields and when user click on cancel button page becomes very slow.
When I remove
ref={register}
from my page then form don't render to many times, but form does not submit with the data.
Is there any way to stop extra rendering?
Here is my code and logic.
Thanks for your attention.
import React, { useState } from "react";
import { useForm } from "react-hook-form";
function Test() {
const { register, handleSubmit } = useForm();
const [edit, setEdit] = useState(false);
const onSubmit = (data) => {
console.log(data);
};
return (
<div className="App">
<header className="App-header">
{console.log("I am redering.")}
<form onSubmit={handleSubmit(onSubmit)}>
<a
href=""
onClick={(e) => {
setEdit(!edit);
e.preventDefault();
}}
>
{edit ? "Cancel" : "Edit"}
</a>
{edit && (
<p>
<input
type="text"
name="email"
ref={register}
placeholder="Email"
/>
</p>
)}
{edit && (
<p>
<input
type="text"
name="firstname"
ref={register}
placeholder="First Name"
/>
</p>
)}
{edit && (
<p>
<input
type="text"
name="lastname"
ref={register}
placeholder="Last Name"
/>
</p>
)}
{edit && (
<>
<input
type="checkbox"
name="contact[]"
id="contact-one"
value="1"
ref={register}
/>
<label htmlFor="contact-one">One</label>
</>
)}
{edit && (
<>
<input
type="checkbox"
name="contact[]"
id="contact-two"
value="2"
ref={register}
/>
<label htmlFor="contact-two">Two</label>
</>
)}
{edit && <button type="submit">Submit</button>}
{edit === false && (
<>
<p>{`My First Name`}</p>
<p>{`My Last Name`}</p>
<p>{`My Email address`}</p>
<p>{`My Contacts`}</p>
</>
)}
</form>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default Test;
<form onSubmit={handleSubmit(onSubmit)}>
This is a function call, which means this gets called immediatelt the form is created on the DOM, your event listeners need to take a reference to the function which will then get called when the event occurs, to do that you wrap this function call in another anonymous function. Then the function will get called when the submit event occurs. So you need to do this
<form onSubmit={()=>{handleSubmit(onSubmit)}}>
Also since this is a submit event, you might want to stop the default behaviour which refreshes the page. Like this
<form onSubmit={(ev)=>{
ev.preventDefault()
handleSubmit(onSubmit)}}>
Update
I did not look at the JSX properly. There are a few things you are probably doing wrong
using ref to get input values rather than state
when taking in any kind of input you want to convert those fields into control components Officila docs on controlled components
I suggest you understand this and state first, that will help you a lot. React docs still uses class components in example code but simply use use state instead of this.state for state varibales
Based on the official Giphy demo(CodeSandBox), I would like to create a live search function for Giphy GIFs.
And below is a demo of it.
search demo(CodeSandBox)
It holds the keyword as state and passes the keyword state to the search method of giphyFetch.
But the search results are not displayed.
Is there a problem with the code in the demo, or a solution to this problem?
Thank you.
source code
const giphyFetch = new GiphyFetch("sXpGFDGZs0Dv1mmNFvYaGUvYwKX0PWIh");
function App() {
const [keyword, setKeyword] = useState("");
const fetchGifs = (offset: number) =>
giphyFetch.search(keyword, { offset, limit: 10 });
return (
<>
<p>
<img src="./logo.gif" width="200" alt="Powered by GIPHY" />
</p>
<p>
input keyword
<input type="text" onChange={e => setKeyword(e.target.value)} />
</p>
<h4>search result</h4>
<Carousel fetchGifs={fetchGifs} gifHeight={200} gutter={6} />
</>
);
}
The Carousal does the fetchGifs once upon mount. So you need to force re-mount upon your input onChange. You can do this by adding dynamic key
Like this
...
<>
<p>
<img src="./logo.gif" width="200" alt="Powered by GIPHY" />
</p>
<p>
input keyword
<input
value={keyword}
type="text"
onChange={e => setKeyword(e.target.value)}
/>
</p>
<h4>search result</h4>
<Carousel
key={keyword}
fetchGifs={() => fetchGifs(5)}
gifHeight={200}
gutter={6}
/>
</>
...
Working demo is here
Still a junior with React and I'm trying to detect an input from the front end and use useState to collect the input data and post it through the console. I followed React's own tutorial but I can't get this right. It's probably something small I'm forgetting but I've been stuck on this for days. Please help.
function Cards2() {
const [input, setInput] = useState();
console.log(input);
return (
<div id="cards" style={{width: 'auto', margin: '10px'}}>
<div className="card-header" id="card-header" style={{backgroundColor: 'dimgray', color: 'white'}}>
<h5>Header</h5>
</div>
<div className="card-body" id="card-body" style={{backgroundColor: 'lightgrey'}}>
<blockquote className="blockquote mb-0">
<p>Enter Name:</p>
<input onChange={() => setInput(input)} type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"/>
<button type="submit" className="btn btn-primary" style={{marginTop: '7px'}}> Submit </button>
<footer className="blockquote-footer">
Someone famous in <cite title="Source Title">Source Title</cite>
</footer>
</blockquote>
</div>
</div>
);
}
export default Cards2;
Your onChange handler in Input needs to be like this:
<input onChange={(event) => setInput(event.target.value)} type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"/>
You can use useEffect as suggested in previous answer but that doesn't really matter.
You'll need to use useEffect to track any changes to the input value (or any state value, for that matter). The following link also explains how useEffect works, and how to work it with input.
https://daveceddia.com/useeffect-hook-examples/
import React, { useState, useEffect } from 'react';
function Cards2() {
const [input, setInput] = useState();
useEffect(() => {
console.log(input);
}, [input]);
return (
// your code here
);
}
export default Cards2;
I want to customize the input tag for file upload.
This is my code. Here for the attribute htmlFor, I am giving id of the input tag.Then it is working. But instead I want to use useRef ref. How can I do that ? If I follow the below method, it will be problematic if I render this component more than once. right ?
const App = () => {
const inputRef = useRef(null);
const [file, setFile] = useState(null);
return (
<>
<input
ref={inputRef}
accept=".pdf"
style={{ display: "none" }}
id="raised-button-file"
multiple
type="file"
onChange={e => {
setFile(e.target.files[0]);
}}
/>
<label htmlFor="raised-button-file">
<button component="span">
<span>file</span>
</button>
</label>
</>
);
};
Another way of using <label> tag is by wrapping your element as a child without specifying an id for it.
<label>
<input
accept=".pdf"
style={{ display: "none" }}
multiple
type="file"
onChange={e => {
setFile(e.target.files[0]);
}}
/>
<span>File</span>
</label>
If you prefer to open your file input dialog with your ref, you can do like this.
const handleOpenFileInput = () => {
inputRef.current.click();
};
<label onClick={handleOpenFileInput}>
<button>file</button>
</label>
<input
ref={inputRef}
accept=".pdf"
style={{ display: "none" }}
multiple
type="file"
onChange={e => {
setFile(e.target.files[0]);
}}
/>
If you use userRef it won't solve your problem. The problem is in the label and the htmlFor attribute. It constantly gets the inputs whose ids match the htmlFor attribute, and since your render the component multiple times, it always gets the first match.
I would simply pass the id of each component as a property, so that each time the label matches the right input. I would change the code to look more like this:
const Form = ({id}) => {
const onChangeInput = e => {
const [file] = e.target.files
}
return (
<form>
<div>
<input type="file" id={id} name="file" className="my-input" accept="application/pdf" style={{display:"none"}} onChange={onChangeInput} multiple/>
<label htmlFor={id}>Upload</label>
</div>
</form>
)
}
function App() {
return (
<div className="App">
<Form id="form1"/>
<Form id="form2"/>
<Form id="form3"/>
</div>
);
}
To make sure that each document has been uploaded correctly, I passed a className attribute to the inputs so I can get all of them. Running this code, I find all files that I have uploaded
Array.from(document.querySelectorAll('.my-input')).map(v => v.files[0].name)
In Material UI Documentation, they showed how to create an "Upload" button by hiding the input file and then adding the Button component inside the label. (see: https://material-ui.com/demos/buttons/)
Now, I want a different button, so I'm working with ButtonBase but it's not working - file select pop-up doesn't show. I'm not sure if I'm missing anything, maybe I'm missing some parameter to pass it?
<input
accept="image/*"
className="d-none"
id="image-upload"
type="file"
/>
<label htmlFor="image-upload"
className="d-block" >
<Button component="span">
Upload
</Button> {/* working */}
<ButtonBase>
test
</ButtonBase> {/* not working*/}
</label>
ButtonBase API: https://material-ui.com/api/button-base/
First, what version are you running? Material-UI is a very fast project so you need to make sure you're checking the documentation for whatever version you are at.
I favor using explicit events (ref in this case) and this works for me under 3.1.0
<input
ref={'file-upload'}
type='file'
/>
<ButtonBase
onClick={e => {
this.refs['file-upload'].click()
}}
>
<div style={{
color: 'red',
backgroundColor: 'yellow',
border: '1px solid green',
}}
>
Upload!
</div>
</ButtonBase>
<hr />
<Button
type='file'
onClick={e => {
this.refs['file-upload'].click()
}}
>
File Upload Material
</Button>
I use something similar to this in one of my projects and I just hide the <input type='file' /> element.
the same can be implement with hooks
export default function myForm(props) {
const inputEl = React.useRef(null);
const onButtonClick = () => {
console.log("inside")
// `current` points to the mounted file input element
inputEl.current.click();
};
return (
<React.Fragment>
Upload Photos
<br/>
<input
accept="image/*"
className={classes.input}
id="outlined-button-file"
multiple
type="file"
ref={inputEl}
/>
<label htmlFor="outlined-button-file">
<ButtonBases
onClick={()=>onButtonClick()}
/>
</label>
</React.Fragment>
)}
Don't forget to call onClick inside ButtonBasses component.
export default function ButtonBases(props) {
const classes = useStyles();
return (
<div className={classes.root}>
<ButtonBase
...
onClick={props.onClick}
>
....
</ButtonBase>
))}
</div>
);
}