I have a form that, on submission displays the results of the form, but more importantly, scrolls to a component that shows the results. I am stuck trying to pass in refs and forward refs. All of the demos I've seen have been of components in the same file. My setup is as follows:
The App.js holds two components– Form.js which submits the form and Results.js which displays the results of the form submission. The Results.js component is further down the page so I want to scroll to that component once the user clicks enter on the form.
Here is a codesandbox that demonstrates my setup.
Here is the same code on here:
// App.js
import "./styles.css";
import Form from "./Form";
import Results from "./Results";
export default function App() {
return (
<>
<Form />
<Results />
</>
);
}
// Form.js
import { forwardRef, useState } from "react";
const Form = forwardRef(({ onScroll }, ref) => {
const [name, setName] = useState("");
const onSubmit = (e) => {
e.preventDefault();
onScroll();
};
return (
<form onSubmit={onSubmit} className="tall">
<input value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
});
export default Form;
// Results.js
import { useRef } from "react";
export default function Results() {
const resultsRef = useRef();
function handleScrollToResults() {
resultsRef.current.scrollIntoView({ behavior: "smooth" });
}
return (
<div onScroll={handleScrollToResults}>
<h1>Results</h1>
</div>
);
}
Few things to be corrected.
Results component should forward the ref, not to the Form component.
import { forwardRef } from "react";
const Results = forwardRef((props, ref) => {
return (
<div ref={ref}>
<h1>Results</h1>
</div>
);
});
export default Results;
Form component should receive the ref to Results as a prop (resultRef).
import { useState } from "react";
const Form = ({ resultRef }) => {
const [name, setName] = useState("");
const onSubmit = (e) => {
e.preventDefault();
resultRef.current.scrollIntoView({ behavior: "smooth" });
};
return (
<form onSubmit={onSubmit} className="tall">
<input value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
};
export default Form;
Root component should create the ref using useRef and use it as below. Notice that Form is using the resultRef while Results is instantiating it.
import "./styles.css";
import Form from "./Form";
import Results from "./Results";
import { useRef } from "react";
export default function App() {
const resultRef = useRef(null);
return (
<>
<Form resultRef={resultRef} />
<Results ref={resultRef} />
</>
);
}
Related
I have this error and I started to learn React last week so I couldn't solve the problem.
Uncaught TypeError: contacts is not iterable
at onSubmit (index.js:15:1)
Contacts Component
import List from "./List"
import Form from "./Form"
import {useState,useEffect} from "react"
import React from "react"
function Contacts(){
const [contacts,setContacts] = useState([]);
useEffect(()=>{
console.log(contacts)
},[contacts])
return (
<div>
<List/>
<Form addContact = {setContacts} contacts={contacts}/>
</div>
)
}
export default Contacts
Form Component
import React from "react"
import { useState } from "react"
function Form(addContact,contacts){
const [form,setForm] = useState({fullName : "" , phoneNumber : ""})
const onChangeInput = (event) => { setForm({...form,[event.target.name]: event.target.value}) }
const onSubmit = (event) => {
event.preventDefault();
if(form.name ==="" || form.phoneNumber===""){
return false
}
addContact([...contacts , form]);
}
return (
<form onSubmit={onSubmit}>
Form List
<div><input name="fullName" placeholder="fullName" onChange={onChangeInput}></input></div>
<div><input name="phoneNumber" placeholder="phoneNumber" onChange={onChangeInput}></input></div>
<div>
<button>Add</button>
</div>
</form>
)
}
export default Form
please use props like this
wrap with {}
function Form({ addContact, contacts }){
...
...
...
}
How can I use the value from the input form and only make an api call when the button is submitted?
Right now when I press submit I am receiving back the user name which is what I expect and want to receive, however it doesn't seem to be sending the information back to userCard. I realize right now I'm not calling it in the form but I'm a bit unsure how to approach this one.
import React,{useEffect, useState} from "react";
import {Form, Button} from "react-bootstrap"
import axios from "axios";
import UserCard from "./Card";
const UserForm = () => {
const[user, setUser] = useState("");
const[login, setLogin] = useState("");
const handleChange = (e) =>{
//console.log(e.target.value)
setUser(e.target.value);
}
const handleSubmit = (e) =>{
//e.preventDefault();
console.log("Button was submitted",user);
axios.get(`https://api.github.com/users/${user}`)
.then((res =>
{setLogin(res.data);
})
)
.catch(err => console.log(err));
}
return (
<div className = "Form">
<Form onSubmit={handleSubmit} onChange = {handleChange} spellcheck="false" >
<Form.Group controlId="formName">
<Form.Label> Github Handle </Form.Label>
<Form.Control type="text" placeholder="Enter Handle Here" />
</Form.Group>
<Button onClick = {handleSubmit}>Submit</Button>
</Form>
</div>
)
}
export default UserForm;
import React from "react"
import { NavLink } from "react-router-dom"
import UserForm from "./UserForm"
const UserCard = (props) =>{
return(
<div className = "Card">
<UserForm />
<h1>Hello, {props.login}</h1>
<h2>How is the weather in {props.location}?</h2>
<h3>Here's a little about you </h3>
<p>{props.bio}</p>
<nav>
<ul className = "cardlist">
<li><NavLink to = "/followers" style={{ textDecoration: 'none' }}>Go To Followers</NavLink></li>
<li><NavLink to = "/repos" style={{ textDecoration: 'none' }}>Go To Repos</NavLink> </li>
</ul>
</nav>
</div>
)
}
export default UserCard;
import './App.css';
import React from "react";
import axios from "axios";
import { BrowserRouter, Link, Route } from 'react-router-dom';
import UserCard from "./components/Card";
import Followers from "./components/Followers";
import Repos from "./components/Repos";
import UserForm from "./components/UserForm"
let followerArray = [];
class App extends React.Component {
state = {
user: '' ,
location : '',
bio: '',
followers: [],
repos: []
}
//make api calls after the component mounts --> equivalent to useEffect()
//componentDidUpdate is equivalent to useEffect(,[])
componentDidMount() {
console.log("Component mounted");
//get user data
console.log("finished")
//get repos
}
render()
{
return (
<BrowserRouter>
<div className="App">
<Route exact path = "/">
<UserCard name = {this.state.name}
login = {this.state.user}
location = {this.state.location}
bio = {this.state.bio}
/>
</Route>
<Route path = "/followers" followers ={this.state.followers}>
<Followers />
</Route>
<Route path = "/repos">
<Repos repos={this.state.repos}/>
</Route>
</div>
</BrowserRouter>
);
}
}
export default App;
When you want to get back some values from child component, you can pass a function to child component. In your case:
Define a Callback function in UserCard component and use the callback function data in card:
import React from "react"
import { NavLink } from "react-router-dom"
import UserForm from "./UserForm"
const UserCard = (props) =>{
const[user, setUser] = useState("");
const[login, setLogin] = useState("");
const giveBackDataToCard = (login , user) => {
setLogin(login);
setUser(user);
}
return(
<div className = "Card">
<UserForm />
<h1>Hello, {login}</h1>
<h2>How is the weather in {user.location}?</h2>
<h3>Here's a little about you </h3>
<p>{user.bio}</p>
<nav>
<ul className = "cardlist">
<li><NavLink to = "/followers" style={{ textDecoration: 'none' }}>Go To Followers</NavLink></li>
<li><NavLink to = "/repos" style={{ textDecoration: 'none' }}>Go To Repos</NavLink> </li>
</ul>
</nav>
</div>
)
}
export default UserCard;
Call props function where you want:
import React,{useEffect, useState} from "react";
import {Form, Button} from "react-bootstrap"
import axios from "axios";
import UserCard from "./Card";
const UserForm = () => {
const handleChange = (e) =>{
setUser(e.target.value);
}
const handleSubmit = (e) =>{
e.preventDefault();
axios.get(`https://api.github.com/users/${user}`)
.then((res =>
{
setLogin(res.data);
//------------> For example here
props.giveBackDataToCard(res.data,e.target.value);
})
)
.catch(err => console.log(err));
}
return (
<div className = "Form">
<Form onSubmit={handleSubmit} onChange={handleChange} spellcheck="false" >
<Form.Group controlId="formName">
<Form.Label> Github Handle </Form.Label>
<Form.Control type="text" placeholder="Enter Handle Here" />
</Form.Group>
<Button onClick = {handleSubmit}>Submit</Button>
</Form>
</div>
)
}
export default UserForm;
Well, If your question is if you want send back the data to the userCard from userForm, You should follow these methods.
First method is, You should learn first a Context APIs in react which already mentioned in docs https://reactjs.org/docs/context.html , use it for passing data between a screen.
Second method is, Always call the API at parent level component, I think its a better and easier way. By calling API from parent level component you will have to pass some props to according to your useForm required.
Third method is, The best way to use some state management store, like Mobx, Redux etc... It is a best way. You can then actually pass your state dynamically to your useCard component.
If answer matched you question, that will great, If not tell so that I will delete for community help.
I'm new to react and I'm trying to build a form. I want the page to redirect to the home page once the form is submitted. Is there a way to set useRef to watch the in-built submit function?
I currently have no compiler errors but the page won't redirect.
current code:
import React, {useRef} from 'react'
import {useForm} from 'react-hook-form'
import Button from '#material-ui/core/Button'
import {Redirect} from 'react-router-dom'
export default function Form() {
const submit = useRef(false)
const {register, handleSubmit} = useForm();
const redir = (data) => {
console.log(submit.current)
console.log(data)
return <Redirect to='/' />
}
return (
<div>
<form className="form" ref={submit} onSubmit={handleSubmit(redir)}>
You're returning the Redirect from a handler function but you need to render it. Instead, you could use history.push('/') to do what you want.
import { useHistory } from 'react-router-dom';
//...
export default function Form() {
const submit = useRef(false);
const history = useHistory();
const {register, handleSubmit} = useForm();
const redir = (data) => {
console.log(submit.current);
console.log(data);
history.push('/');
}
return (
<div>
<form className="form" ref={submit} onSubmit={handleSubmit(redir)}>
<input type="submit" value="Submit" />
</form>
</div>
);
}
I am trying to make a movie search function using React, Axios, and movieDB API. The functionality I am trying to implement right now is typing in a movie into the search bar and clicking the submit button will return the movie title as an H1 element.
My onClick function does not work: <button onClick={(e)=>clickHandler()}>submit</button>
componentDidMount() will work only when the page refreshes and you cannot search for anything as the submit button is broken.
I am not sure how to implement this, but I would also not mind if I could get it to search by hitting enter instead of using a button, whichever is easier.
Here is my code so far.
App.js
import React from "react"
import Movielist from './components/Movielist'
function App() {
return (
<div>
<input type="search" id="search" />
<button onClick={(e)=>clickHandler()}>submit</button>
<h1 id="title">title</h1>
<Movielist />
</div>
)
}
export default App
Movielist.js
import React from 'react';
import axios from 'axios';
export default class Movielist extends React.Component {
state = {
title: ""
}
componentDidMount() {
const API_KEY = '***********************';
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
this.setState({ title });
})
}
render() {
return (
<h1>{this.state.title}</h1>
)
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
import React from 'react';
import axios from 'axios';
export default class Movielist extends React.Component {
state = {
title: ""
}
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = '***********************';
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
this.setState({ title });
})
}
}
render() {
return (
<input type="search" id="search" onKeyDown={event => this.clickHandler(event)} />
<h1>{this.state.title}</h1>
)
}
}
You should call API to get the movie list after hitting the button, then pass the data that you've got to Movielist. Try this:
In App.js:
import React from "react"
import axios from 'axios'
import Movielist from './components/Movielist'
function App() {
const [movieList, setMovieList] = React.useState([])
const handleOnSubmit = () => {
const API_KEY = '***********************';
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
setMovieList(res.data['results'])
})
}
return (
<div>
<input type="search" id="search" />
<button onClick={handleOnSubmit}>submit</button>
<h1 id="title">title</h1>
<Movielist movieList={movieList}/>
</div>
)
}
export default App
In Movielist.js:
import React from 'react';
const Movielist = ({movieList}) => {
return (
<div>
{
movieList.map(movie => <h1 key={movie.key}>{movie.title}</h1>)
}
<div/>
)
}
}
export default Movielist
import React, {useState} from "react"
import axios from 'axios';
import Movielist from './components/Movielist'
const [title, setTitle] = useState("")
const API_KEY = '***********************'
function App() {
const clickHandler = () => {
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
setTitle(title);
})
}
return (
<div>
<input type="search" id="search" />
<button onClick={clickHandler}>submit</button>
<h1 id="title">title</h1>
<Movielist title={title} />
</div>
)
}
export default App
just move call api handle to your onclik func then pass title props to movie list
If you want to query the API after user push submit button. You should put your call to API in the call handler, then pass the state from App to MovieList as props
export class App extends React.Component {
state = {
title: ""
}
clickHandler() {
const API_KEY = '***********************';
const query = document.getElementById('search').value;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`).then(res => {
const title = res.data['results'][0]['title'];
this.setState({ title });
});
}
render() {
return (
<div>
<input type="search" id="search" />
<button onClick={(e)=>clickHandler()}>submit</button>
<h1 id="title">title</h1>
<Movielist list={this.state.title}/>
</div>
)
}
}
export class MovieList extends React.Component {
render() {
<h1>{this.props.title}</h1>
}
}
Alternatively, you can wrap the input in a element and use onSubmit + evt.preventDefault() instead, by doing so you can handle button click and pressing "Enter" to submit.
I'm trying to get this form working for the first time and would just like to know that my onclick is at least working. I'd like to inject a spy to replace the handler that my dispatchToProps is referencing as well.
So in other words I'd like to replace this:
AsyncActions.login
with loginSpy
I can't just do button.props().login = loginSpy because props are immutable at that point. I get TypeError: Can't add property login, object is not extensible
So is there a way to use restructuring through an ES6 class, specifically an ES6 react component via its constructor or something like that?
I know you can do {prop1, prop2} as a parameter in a stateless function, for example:
function FieldGroup({ id, label, help, ...props }) {
but what about ES6 classes in React?
Test
it.only('can log in successfully', async () => {
const container = shallow(<LoginContainer store={store} />),
loginContainer = shallow(<LoginContainer store={store} />),
login = loginContainer.dive().find(Login),
loginForm = login.dive().find(LoginForm),
loginFormLogin = await loginForm.props().login(),
button = loginForm.dive().find('.ft-login-button'),
loginSpy = spy()
button.props().login = loginSpy
button.simulate('click')
expect(loginSpy.calledOnce).to.be.true
})
Container
import { connect } from 'react-redux'
import React, { Component } from 'react'
import * as AsyncActions from '../actions/User/UserAsyncActions'
import Login from '../components/Login/Login'
class LoginContainer extends Component {
componentWillMount(){
// const requested = this.user.requested
}
render(){
return( <Login login={this.props.login} /> )
}
}
const mapStateToProps = state => {
return {
requesting: state.user.requesting,
token: state.user.token,
session: state.user.session
}
}
export const mapDispatchToProps = {
login: AsyncActions.login
}
export { Login }
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
LoginForm
import React, { Component } from 'react'
import { Button, FormControl, FormGroup, ControlLabel, PageHeader } from 'react-bootstrap'
class LoginForm extends Component {
render(){
return (
<div className='ft-login-form'>
<PageHeader className='ft-header'>Login</PageHeader>
<form>
<FormGroup controlId="formBasicText" >
<ControlLabel>Email</ControlLabel>
<FormControl
bsSize="small"
className="ft-username"
componentClass="input"
placeholder="Enter mail"
style={{ width: 300}}
type="text"
/>
<ControlLabel>Password</ControlLabel>
<FormControl
bsSize="small"
className="ft-password"
componentClass="input"
placeholder="Enter Password"
style={{ width: 300}}
type="text"
/>
</FormGroup>
<Button
className='ft-login-button'
onClick={this.props.login}
type='submit'>Login</Button>
</form>
</div>)
}
}
export default LoginForm
You should shallow render LoginForm instead of LoginContainer and simply pass loginSpy as a prop to LoginForm to test the button...
it.only('can log in successfully', async () => {
const loginSpy = spy(),
loginForm = shallow(<LoginForm login={loginSpy} />),
button = loginForm.dive().find('.ft-login-button')
button.simulate('click')
expect(loginSpy.calledOnce).to.be.true
})