I am working on a simple search bar. The input label read a string and once the content is changed the state inputString is updated. Once the user click the 'search' button, we split the 'inputString' and assign the string[] to state 'keywords', then such an array will be used to send a request.
I still have limited knowledge of asynchronous issues in JS. So I'd like to know how can I make sure every time I click the button, I split the newest inputString? (console.log tells me I don't, but is that another asynchronous issue?)
import React, { useState } from "react";
export default function Search(){
const [inputString, setInputString] = useState('');
const [keywords, setKeywords] = useState([]);
function sendSearchKeywords(){
const InputStringRemovedSpace = inputString.replace(/ +(?= )/g,'');
setKeywords(InputStringRemovedSpace.split(' '));
console.log(inputString);
//do something about request here
console.log(keywords);
}
return (
<div>
<div>
<input type='text' placeholder="enter your keywords" onChange={(e)=>{
setInputString(e.target.value);
}}/>
<button onClick={()=>{
sendSearchKeywords();
}}>Search</button>
</div>
</div>
);
}
React setState is not asynchronous but we can use useEffect hook.
import React, { useState } from "react";
export default function Search(){
const [inputString, setInputString] = useState('');
const [keywords, setKeywords] = useState([]);
React.useEffect(() => {
console.log(inputString);
//do something about request here
console.log(keywords);
}, [keywords]);
function sendSearchKeywords(){
const InputStringRemovedSpace = inputString.replace(/ +(?= )/g,'');
setKeywords(InputStringRemovedSpace.split(' '));
}
return (
<div>
<div>
<input type='text' placeholder="enter your keywords" onChange={(e)=>
{
setInputString(e.target.value);
}}/>
<button onClick={()=>{
sendSearchKeywords();
}}>Search</button>
</div>
</div>
);
}
Related
I am trying to learn the basics of React and thought that making a todo list app would be a great first project to help me learn.
I have a basic form to add todos, but, when enter is clicked, the DOM does not change. Here is my app.js code, which I think is where my error might be:
import AddTodoForm from './components/AddTodoForm.js';
import TodoList from './components/TodoList.js';
import { dataList } from './components/AddTodoForm.js';
import { useState } from 'react';
function App() {
const[list, setList] = useState([]);
function update(){
setList(dataList);
console.log(list);
console.log("update function has run.")
}
return (
<div>
<AddTodoForm update = {update} />
<h1>My Todos</h1>
<TodoList todos={list} />
</div>
);
}
export default App;
Here is the code for TodoList.js as somebody had asked for it:
import Todo from './Todo';
function TodoList(props) {
return (
<ul>
{props.todos.map((todo) => (
<Todo
key = {todo.id}
id= {todo.id}
text= {todo.text}
/>
))}
</ul>
)
}
export default TodoList;
here is the AddTodoForm.js:
import { useRef, useState } from 'react';
var idCounter = 1;
export const dataList = [];
function AddTodoForm(props){
const titleInputRef = useRef();
function submitHandler(event){
event.preventDefault();
const enteredTitle= titleInputRef.current.value;
const todoData = {
text: enteredTitle,
id: idCounter,
}
idCounter++;
console.log(todoData);
dataList.push(todoData);
}
return (
<div className="card">
<h2>Add New Todos</h2>
<form onSubmit={(event) => {submitHandler(event); }}>
<div>
<label htmlFor="text">New Todo: </label>
<input type="text" required id="text" ref={titleInputRef}></input>
</div> <br />
<div>
<button className="btn" onClick = {props.update}>Add Todo</button>
</div>
</form>
</div>
)
}
export default AddTodoForm;
I have checked the log and the update function runs. Also, if I make a slight change to my code, the todos I had entered will appear on the screen but I am not sure why the DOM does not change when the update function runs.
This is my first post on here so I wasn't sure how much of my code to include. Please ask if you need the code from my other components.
Many thanks in advance :)
Calling dataList.push(todoData) won't change dataList itself, only its content, and React doesn't check the content to update the DOM. You could use the Spread syntax to have a completely new dataList.
You could even get rid of that dataList, and use the empty array given to useState. Update your update function slightly, and it should work:
import AddTodoForm from "./components/AddTodoForm.js";
import TodoList from "./components/TodoList.js";
import { useState } from "react";
function App() {
const [list, setList] = useState([]);
function update(text) {
// this is for learning; consider using a proper id in real world
setList([...list, { text: text, id: list.length + 1 }]);
}
return (
<div>
<AddTodoForm update={update} />
<h1>My Todos</h1>
<TodoList todos={list} />
</div>
);
}
export default App;
import { useRef } from "react";
function AddTodoForm(props) {
const titleInputRef = useRef();
function submitHandler(event) {
event.preventDefault();
props.update(titleInputRef.current.value);
}
return (
<div className="card">
<h2>Add New Todos</h2>
<form onSubmit={submitHandler}>
<div>
<label htmlFor="text">New Todo: </label>
<input type="text" required id="text" ref={titleInputRef}></input>
</div>
<br />
<div>
<button className="btn">Add Todo</button>
</div>
</form>
</div>
);
}
export default AddTodoForm;
In App.js, I have removed the update function and instead sent the list and setList as props to the AddTodoForm component.
import AddTodoForm from './components/AddTodoForm.js';
import TodoList from './components/TodoList.js';
import { useState } from 'react';
function App() {
const[list, setList] = useState([]);
return (
<div>
<AddTodoForm setList = {setList} list = {list}/>
<h1>My Todos</h1>
<TodoList todos={list} />
</div>
);
}
export default App;
In ./components/AddTodoForm.js add this peice of code inside the AddTodoForm function.
const update = ()=>{
props.setList(datalist);
console.log(props.list);
console.log("The update function has run.")
}
I hope this might help.
I am going to post my opinion to help you with my knowledge about React. In your above code, you cannot render updated state(list) to reflect TodoList component.
In hook, useEffect insteads 2 component lifecycles of class component idea. I mean, you should reflect useEffect with updated states(list).
useEffect is similar to componentDidMount and componentDidUpdate.
Like this:
enter code here
useEfect(()=>{
setList(dataList);
console.log(list);
console.log("update function has run.")
},[list])
I am having some issues figuring out how I can get the state of an inputfield, and add it to an useState array.
The way this code is set up, using onChange, it will add every character I type in the textField as a new part of the array, but I dont want to set the value until the user is done typing.
What would be a simple solution to this?
My code:
const [subject, setSubject] = useState([]);`
<input type="text" placeholder={"Eks. 'some example'"} onChange={(e) => setSubject(oldArray => [...oldArray, e.target.value])}/>
Well, I am not confident with react yet, but unless you don't want to do some validation, why don't you use useRef hook and onBlur combination. UseRef hook basically set a reference on element and then you can use value from that reference which in your case would be textField value. OnBlur will trigger when user clicks outside of input (input lose focus) Code should look like this:
import react, {useRef, useState} from "react";
const someComponent = (props) => {
const [subject, setSubject] = useState([]);
const textAreaRef = useRef();
const onBlurHandler = () => {
setSubject((prevSubject) => [...prevSubject, textAreaRef.current.value]);
}
return <input type="text" placeholder={"Eks. 'some example'"} ref={textAreaRef} onBlur={onBlurHandler}/>
}
Other way would be to use debouncing with useEffet.
this is a little something i cooked up for you... it watches the change of the input, and 1 second after the person stops typing, it will add the input value.
The main things to look at here are the useEffect() and the <input /> with the new state i made [input, setInput]. or you can play around with this here
export default function App() {
const [subjects,setSubjects] = useState([]);
const [input,setInput] = useState("")
useEffect(() => {
const timer = setTimeout(() => {
setSubjects(old => [...old, input])
}, 1000)
return () => clearTimeout(timer)
}, [input])
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<input placeholder="type here"
value={input}
type="text"
onChange={e => setInput(e.target.value)}
/>
{subjects.length === 0 ?
<h3>Nothing yet...</h3>
:
<h3>{subjects}</h3>
}
</div>
);
}
I am a newbie in ReactJS and I am making a simple contact manager.
In my InputContact component I take name and email of contact and after submission I store in state variables and pass to parent.
To check my state var is updated , i check my console.
The problem is that, after I submit the form after giving data, I only see a blank line in console. After again clicking on submit, then I see my input in console.
My question is
Why I have to click submit twice , in order to see my state variable getting updated ??
My InputContact.js file
import React from 'react'
import { useState } from 'react';
const InputContact = (props)=>{
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const validateInput = (e)=>{
e.preventDefault();
setName(e.target.fname.value);
setEmail(e.target.femail.value);
console.log(name)
props.addContact(name,email);
}
return(
<>
<form onSubmit={validateInput}>
<label>Name
<input type="text" name='fname' ></input>
</label>
<br/>
<label>Email
<input type="text" name='femail' ></input>
</label>
<button type="submit">Save</button>
</form>
</>
)
};
export default InputContact;
My App.js file is
import Header from './components/Header/Header'
import InputContact from './components/InputContact/InputContact';
import { v4 as uuidv4 } from 'uuid';
function App(){
const [contacts, setContacts] = useState([]);
const addContactFn= (name,email)=>{
setContacts([...contacts, {id:uuidv4(), name:name, email:email}]);
}
return(
<>
<Header />
<InputContact addContact = {addContactFn}/>
</>
)
}
export default App; ```
Your setName call is asynchronous. You cannot guarantee that
console.log(name)
right after
setName(e.target.fname.value);
How you are using state isn't really the normal way. You want to use onChange handlers on the inputs to set the state for each name and email, e.g.
onChange={(e)=> setEmail(e.target.value)}
Then onSubmit of your form should refer to the state variables for name and email, not e.target.value
const validateInput = (e)=>{
e.preventDefault();
props.addContact(name,email);
}
To check the updated value, you can use useEffect hook as
import React from 'react'
import { useState, useEffect } from 'react';
.
.
.
useEffect(() => {
console.log('Name', name);
}, [name]);
const validateInput = (e)=>{
e.preventDefault();
setName(e.target.fname.value);
setEmail(e.target.femail.value);
props.addContact(name,email);
}
.
.
.
I do it like this, so the state updates on typing and when you send it, is already updated.
<form onSubmit={validateInput}>
<label>Name
<input type="text" name='fname' onChange={e => setName(e.target.value) ></input>
</label>
<br/>
<label>Email
<input type="text" name='femail' onChange={e => setEmail(e.target.value) ></input>
</label>
<button type="submit">Save</button>
</form>
I have a react component which manage user logging in and out, when user type email and password in the login field the whole component (Navbar) re-render to Dom in every keystroke unnecessarily thus reduces speed.
How can I prevent Navbar from re-rendering when user type their credential in login fild ?
import React, { useContext,useState } from 'react';
import { Postcontext } from '../contexts/Postcontext';
import axios from 'axios';
const Navbar = () => {
const { token,setToken } = useContext(Postcontext);
const [email,setEmail] = useState(''); **state manages user email for login**
const [password,setPassword] = useState(''); **state manages user password for login**
const[log,setLog] = useState(true) **state manages if user logged in or not based on axios post request**
const login=(e)=>{
//function for login using axios
})
}
const logout=(e)=>{
//function for logout using axios
}
return (
<div className="navbar">
{log?(
<form>
<input value={email} type="text" placeholder="email" onChange={(e)=>setEmail(e.target.value)}/>
<input value={password} type="text" placeholder="password" onChange={(e)=>setPassword(e.target.value)}/>
<button onClick={login}>login</button>
</form>
):(
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default Navbar;
It is because it is same component which needs re-render to reflect input text changes. If you want your email to change but not effect Navbar then create a child component and move inputs into that component, manage input values using useState() there in child component and when you finally submit and user is logged in then you can either update some global state like redux store or global auth context to reflect and rerender Navbar.
So, I had the same issue and I was able to solve it using useRef and useCallback and I will try to explain in Q&A form. Sorry if I am not that clear, this is my first StackOverFlow comment and I am a beginner in React :)
Why useRef?
React re-renders every time it sees a component has updated by checking if previous and current object are same or not. In case of useRef it checks the object Id only and not the content inside it i.e. value of current inside the Ref component. So if you change the value of current React will not consider that. (and that's what we want)
Why useCallback?
Simply because it will run only when we call it or one (or more) of the dependencies have changed. As we are using Ref so it won't be called when the current value inside it has changed.
More info: https://reactjs.org/docs/hooks-reference.html
Based on above info your code should look like this (only doing login part):
import React, { useContext, useRef } from 'react';
const App = () => {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const logRef = useRef(null);
const loginUpdate = useCallback( async (event) => {
event.preventDefault();
// Your logic/code
// For value do:
// const email = emailRef.current.value;
}, [emailRef, passwordRef, logRef]);
return (
<div className="navbar">
{log?(
<form>
<input
ref={emailRef}
type="text"
placeholder="email"
/>
<input
ref={passwordRef}
type="text"
placeholder="password"
/>
<button onClick={loginUpdate}>login</button>
</form>
):(
// Not doing this part because I am lazy :)
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default App;
Had a few typos. It works for me
https://codesandbox.io/s/cold-sun-s1225?file=/src/App.js:163-208
import React, { useContext,useState } from 'react';
// import { Postcontext } from '../contexts/Postcontext';
// import axios from 'axios';
const App = () => {
// const { token,setToken } = useContext();
const [email,setEmail] = useState('');
const [password,setPassword] = useState('');
const[log,setLog] = useState(true)
const login=(e)=>{
//function for login using axios
}
const logout=(e)=>{
//function for logout using axios
}
return (
<div className="navbar">
{log?(
<form>
<input value={email} type="text" placeholder="email" onChange={(e)=>setEmail(e.target.value)}/>
<input value={password} type="text" placeholder="password" onChange={(e)=>setPassword(e.target.value)}/>
<button onClick={login}>login</button>
</form>
):(
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default App;
I'm building a dropdown with suggestions that fetch data from an API. The input from the search bar is being stored using setState and it is updated when i change the value in the text input.
The thing is that I'm not managing to update the users lists from the dropdown each time I enter a new character in the text input. Can I somehow force the component to be rendered every time the props change? Or is there a better approach?
import React, {useState, useEffect} from 'react';
import Dropdown from '../Dropdown/Dropdown';
import './SearchBar.css';
// Component created with arrow function making use of hooks
const SearchBar = (props) => {
const [input, setInput] = useState('');
const [dropdownComponent, updateDropdown] = useState(<Dropdown input={input}/>)
useEffect(() => {updateDropdown(<Dropdown input={input}/>)}, [input])
const onChange = (e) => {
setInput(e.currentTarget.value)
updateDropdown(<Dropdown input={input}/>)
console.log("=(")
}
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
{dropdownComponent}
</div>
)
}
export default SearchBar;
I can't make the problem happen using your code in a simple test, but your onChange does has a problem: It's using input to update the dropdown, but it's not using useCallback to ensure that input isn't stale when it does. Either:
Don't update the dropdown in your onChange, allowing your useEffect callback to do it; or
Use e.target.value instead of input and get rid of the useEffect updating the dropdown; or
Don't memoize the dropdown (e.g., don't put it in state) since you want to update it when the input changes anyway, just render it directly in the JSX
Of those, with what you've shown, #3 is probably the simplest:
const SearchBar = (props) => {
const [input, setInput] = useState('');
const onChange = (e) => {
setInput(e.currentTarget.value);
};
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
<Dropdown input={input}/>
</div>
);
}
Live Example:
const {useState, useEffect} = React;
function Dropdown({input}) {
return <div>Dropdown for "{input}"</div>;
}
const SearchBar = (props) => {
const [input, setInput] = useState('');
const onChange = (e) => {
setInput(e.currentTarget.value);
};
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
<Dropdown input={input}/>
</div>
);
}
ReactDOM.render(<SearchBar />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>