I have recently learning react for front end development and have encountered the problem when using useStates. I tried to input a value in a textbox and submit it by clicking a submit button. Normally, only 1 response would be logged on the console, however it appeared twice.
Would be grateful if someone could spot where went wrong in my code snippet.
`
import './App.css';
import { Button } from 'react-bootstrap';
import { useState } from "react";
import axios from 'axios';
function App () {
const [key, setKey] = useState(null);
const [submit, setSubmit] = useState(false);
function getKey(val){
setKey({[val.target.name]: val.target.value})
}
{
console.log(key)
axios
.post('https://jsonplaceholder.typicode.com/posts', key)
.then(response => {
console.log(response)
})
}
return (
<>
<div className = "App">
<h1>Type in the keyword you wish to search: </h1>
<input
type = "text"
name = "keyword"
onChange = {getKey}
/>
<Button onClick = {() => setSubmit(true)} > Submit!</Button>
</div>
</>
);
}
export default App;
`
Related
I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange
I'm trying to fetch data in react. The problem is i have to click on button twice to get that data.
Although i don't get data on first click it somehow renders if I add JSON.stringify to it. If I don't add JSON.stringify it returns undefined. If anyone know what this is please help me
without clicking
on first click
on second click
import React, {useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios'
function Example() {
const [students,setStudents] = useState('')
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents(data)
console.log(students)
}
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{JSON.stringify(students.data)}
</div>
</div>
);
}
export default Example;
if (document.getElementById('root')) {
ReactDOM.render(<Example />, document.getElementById('root'));
}
The problem was that setStudents is an asynchronous function, so I just made student object and added to it loading property
const [students,setStudents] = useState({
data: '',
loading: true
})
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents({
data: data,
loading: false
})
}
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{students.loading?'':
students.data.data[0].name}
</div>
</div>
);
}
setStudent is an asynchronous function. This means the value of students won't change immediately after you call setStudents.
Try shifting the console.log outside the handleClick function. Like this -
import React, {useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios'
function Example() {
const [students,setStudents] = useState('')
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents(data)
}
console.log(students)
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{JSON.stringify(students.data)}
</div>
</div>
);
}
export default Example;
if (document.getElementById('root')) {
ReactDOM.render(<Example />, document.getElementById('root'));
}
Initially, the value will be an empty string, then it will change to the value from api/foo
React hooks are async so when you are running console.log(students) right after running setStudents(data) it is still not populated, however the 2nd time you click the button it is already populated from the first time you clicked it.
If you want to console the result right after the state setter runs you can see this answer on another question.
I have a searchbar component and a Catalog component. The Catalog component contains different cards. Depending on what is typed in the input field of the searchbar component I want to render different cards.
For this to work I need to be able to import the value of the input field into the Catalog component where it is passed in a search function that handles all the rest of the work.
I am able to import the value into my Catalog component but unfortunaty I can't figure out how I can tell if the imported value has changed so I can search again?
I have found some ways to do this with classes but I would like to use hooks instead. I have experimented a bit with "useEffect" but that didn't work out.
Thank you for your help!
This is my code in the searchbar component:
import React, { useState } from 'react';
let input = "";
function Search() {
const [value, setValue] = useState(input);
function onSearch(e) {
setValue(e.target.value);
input = value;
}
return(
<form className="searchForm">
<input className="search" type="text" name="search" autoComplete="off" placeholder="zoeken" value={value} onChange={onSearch}/> </form>
);
}
export { Search, input };
And this is the code in my Catalog
import React, { useState, useEffect } from 'react';
import {input} from "./search";
// other imports
function Catalog(props){
//get cards code and fuse code
const [query, setQuery] = useState(input);
function inputHasChanged(){ //function that can tell if the imported input variable changed
setQuery(input); //update query and rerender cards
}
const results = fuse.search(query)
const searchedCards = query ? results.map(card => card.item) : cards;
//other code
return(
<div>
//render the SearchedCards
</div>
);
}
export {Catalog};
Solution:
code in search:
import React, { useState } from 'react';
const Search = ({ searching }) => {
const [value, setValue] = useState("");
function submit(e){
setValue(e.target.value);
searching(value);
}
return (
<form className="searchForm">
<input
className="search"
type="text" name="search"
autoComplete="off"
placeholder="zoeken"
value={value}
onChange={submit}
/>
</form>
);
};
export { Search };
Search is a child of banner:
import React, {useState, useEffect} from 'react';
import {Search} from './search';
import Header from './Header';
import Overzicht from './Overzicht';
const Banner = ({ search }) => {
const [value, setValue] = useState("");
useEffect(() => {
search(value);
},[search, value]);
return(
<div className="banner">
<Header />
<Search searching={value => setValue(value)} />
<Overzicht />
</div>
);
};
export default Banner;
Banner is a child of home which also contains Catalog:
import React, { useState } from "react";
import Banner from './banner';
import {Catalog} from './Catalog';
function Home(){
const [input, setInput] = useState("");
return(
<div>
<section id="banner">
<Banner search={input => setInput(input)}/>
</section>
<section id="overzicht">
<Catalog search={input} />
</section>
</div>
);
}
export default Home;
And now I can just call
props.search
In Catalog
You can use useEffect as mentioned below:
useEffect(() => {
// Write your logic here
},[input]); // it will run only when the input changes
Push the common state, the query, up to a common ancestor and pass it down as needed to child and descendant components. This way they can "watch" the changes by having new props passed to them.
Below is a simplified version of a structure that would work:
function Catalog({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
// If `fuse.search` is asynchronous then you might need to debounce
// these queries and/or cancel old queries. If a user types "foo",
// a query is kicked off, and then they finish typing "food", you
// want to cancel the query for "foo" because the results will no
// longer be relevant.
const results = fuse.search(query);
setResults(results);
}, [query])
return (
<div />
);
}
function Search({ query, setQuery }) {
return (
<input onChange={setQuery} value={query} />
)
}
function App() {
const [query, setQuery] = useState("");
return (
<>
<Search query={query} setQuery={setQuery} />
<Catalog query={query} />
</>
);
}
I've set up my context and I have a function that runs once the form is submitted handleSubmit. When I submit the form, I want the results to be shown on a separate page dashboard. I'm using history.push().
My form is wrapped in the withRouter HOC.
When I submit the form, I receive "props.history is undefined"
I also have another function that is using a match.params and I'm getting undefined as well. So I'm assuming it has to do with React Router.
I considered that perhaps my Context file is the one that needs to be wrapped with the withRouter HOC, but the file has two exports.
My Context Provider
import React, { useState, useEffect, createContext } from 'react'
const AnimeContext = createContext()
const API = "https://api.jikan.moe/v3"
const AnimeProvider = (props) => {
const urls = [
`${API}/top/anime/1/airing`,
`${API}/top/anime/1/tv`,
`${API}/top/anime/1/upcoming`,
]
// State for Anime search form
const [dataItems, setDataItems] = useState([])
const [animeSearched, setAnimeSearched] = useState(false)
// Fetch searched Anime
async function handleSubmit(e) {
e.preventDefault()
const animeQuery = e.target.elements.anime.value
const response = await fetch(`${API}/search/anime?q=${animeQuery}&page=1`)
const animeData = await response.json()
setDataItems(animeData.results)
setAnimeSearched(!animeSearched)
props.history.push('/dashboard')
}
return (
<AnimeContext.Provider value={{
topTv,
setTopTv,
topAiring,
setTopAiring,
topUpcoming,
setTopUpcoming,
dataItems,
setDataItems,
animeSearched,
setAnimeSearched,
fetching,
anime,
fetchTopAnime,
fetchAnimeDetails,
handleSubmit
}}>
{props.children}
</AnimeContext.Provider>
)
}
export { AnimeProvider, AnimeContext }
My SearchForm component
import React, { useContext } from 'react';
import { withRouter } from 'react-router-dom'
import styled from 'styled-components'
import AnimeCard from './AnimeCard/AnimeCard';
import { AnimeContext } from '../store/AnimeContext'
const SearchForm = () => {
const { dataItems, animeSearched, handleSubmit } = useContext(AnimeContext)
return (
<div>
<Form onSubmit={handleSubmit}>
<Input
type="text"
name="anime"
placeholder="Enter title"
/>
<FormButton type='submit'>Search</FormButton>
</ Form>
{animeSearched
?
<AnimeCard
dataItems={dataItems}
/>
: null}
</div>
)
}
export default withRouter(SearchForm)
you can always use useHitory hook everywhere!
import { useHistory } from 'react-router'
...
const Page = function(props) {
let history = useHistory();
...
history.push('/')
...
}
In react-router, you would get history from props if any component is rendered as a child or Route or from an ancestor that is renderd form Route and it passed the Router props to it. However it is not receiving Router props, i suggest try this one
You can use Redirect from react-router-dom
import { Redirect } from "react-router-dom";
const [redirect, setRedirect] = useState(false);
Now set the vlue of redirect to true where ever you want
setRedirect(true);
like in your case
async function handleSubmit(e) {
e.preventDefault()
const animeQuery = e.target.elements.anime.value
const response = await fetch(`${API}/search/anime?q=${animeQuery}&page=1`)
const animeData = await response.json()
setDataItems(animeData.results)
setAnimeSearched(!animeSearched)
setRedirect(true);
}
Now you can use the following for the Redirection in return function like so
if(redirect) {
return <Redirect to="/dashboard" />
} else {
return (
<Your-Component />
)
I'm building an application with dynamic forms (redux-form). I would like when user click on submit button to print values. Note that my submit button is placed in my application header (outside the form). To achieve this, I'm following this tutorial from Redux-form. When I press the "Save" button, I got this error in my console : (0 , _reduxForm.submit) is not a function(…).
My code :
Submit component
import React from 'react'
import {connect} from 'react-redux'
import {submit} from 'redux-form'
const RemoteSubmitButton = ({dispatch}) =>
// How to get 'form' prop here ?
<button type="submit" onClick={() => dispatch( submit() ) }>Save</button>
export default connect()(RemoteSubmitButton)
Main component
// Import librairies
import Submit from 'submitBtn'
export default class extends Component {
...
render(){
return (
// html code
<Submit form="newuser" /> // form prop gonna be dynamic
)
}
}
submit.js
import {SubmissionError} from 'redux-form'
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
function submit(values) {
return sleep(1000) // simulate server latency
.then(() => {
window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`)
})
}
export default submit
new.js (New User)
//Import librairies
import submit from 'submit'
class UserForm extends Component {
render() {
const {error, resetForm, handleSubmit} = this.props
return (<form onSubmit={ handleSubmit }>
<!-- Generate dynamic fields -->
</form>)
}
}
let FormWrapper = reduxForm({form: 'newuser', onSubmit: submit})(UserForm)
const selector = formValueSelector('newuser') // <-- same as form name
FormWrapper = connect(state => state.form)(FormWrapper)
Can you tell me what I'm doing wrong? or What can I do to make it work please ?
The submit action was added in v6.2.0 according to the release notes.
You need to upgrade your version of redux-form in order for this to work.
Edit:
In order to submit the form, you need to use the form prop in your RemoteSubmitButton component:
import React from 'react'
import {connect} from 'react-redux'
import {submit} from 'redux-form'
const RemoteSubmitButton = ({ dispatch, form }) => // Destructure the props here
<button type="submit" onClick={() => dispatch( submit(form) ) }>Save</button>
export default connect()(RemoteSubmitButton)