Make a component Rerender on useEffect - reactjs

I'm creating a search bar, which calls an API to return a list of devices with matching names.
Ideally, when the user first looks at the component, it sees just a search bar. Once the user types in the search bar, the API returns a list of matching names. A list of these names is then shown below the search bar.
I'm trying to do this with hooks, but I can't get the list to show / the component to update and show the new list.
What am I missing, is this the correct way to go about this?
const Search = () => {
const [input, setInput] = useState("");
let devices = [];
const handleChange = e => {
setInput(e.target.value.toUpperCase());
};
useEffect(() => {
apiService.getDevices(input).then(response => {
console.log("response:", response); // This brings back the response correctly
const newDevices = response.map(device => <li key={device}>{device}</li>);
devices = <ul>{newDevices}</ul>;
});
}, [input]);
return (
<Fragment>
<div>
<div className="form-group">
<div className="form-group__text">
<input
type="search"
onChange={handleChange}
placeholder="Search device by serial number"
/>
<button type="button" className="link" tabIndex="-1">
<span className="icon-search"></span>
</button>
</div>
</div>
<div>{devices}</div>
<p>testestes</p>
</div>
</Fragment>
);
};

Store devices in state, and then do the map rendering directly in the return, like so:
const Search = () => {
const [input, setInput] = useState("");
const [devices, setDevices] = useState([]);
const handleChange = e => {
setInput(e.target.value.toUpperCase());
};
useEffect(() => {
apiService.getDevices(input).then(response => {
setDevices(response);
});
}, [input]);
return (
<Fragment>
<div>
<div className="form-group">
<div className="form-group__text">
<input
type="search"
onChange={handleChange}
placeholder="Search device by serial number"
/>
<button type="button" className="link" tabIndex="-1">
<span className="icon-search"></span>
</button>
</div>
</div>
<div>
<ul>
{devices.map(device => <li key={device}>{device}</li>)}
</ul>
</div>
<p>testestes</p>
</div>
</Fragment>
);
};
A component will only re-render when the props or state change, so a useEffect cannot trigger a rerender without also updating some state

Related

Retrieve form input values using React Children as a wrapper

I need to retrieve the input form data using the wrapper save button (Togglable component) and not a submit button inside a form component.
I have this form.
const BasicDataForm = () => {
return (
<form>
<div>
<label htmlFor="nameInput">Your Name: </label>
<input placeholder="Write your name here" type="text" id="nameInput" />
</div>
<div>
<label htmlFor="ageInput">Your Age: </label>
<input placeholder="Write your age here" type="number" id="ageInput" />
</div>
</form>
);
};
And the wrapper
const Togglable = ({ title, children }) => {
const [visible, setVisible] = useState(false);
const hideWhenVisible = { display: visible ? 'none' : '' };
const showWhenVisible = { display: visible ? '' : 'none' };
return (
<header>
<h2>{title}</h2>
<div style={hideWhenVisible}>
<button onClick={() => setVisible(true)}>Show</button>
</div>
<div style={showWhenVisible}>
<button>Save</button> {/* This button needs to retrieve all input form data from children */}
<button onClick={() => setVisible(false)}>Close</button>
{children}
</div>
</header>
);
};
And the main component
const App = () => {
return (
<>
<h1>Form Wrapper</h1>
<Togglable title="Basic Data Form">
<BasicDataForm />
</Togglable>
</>
);
};
I don't know if it's necessary to add a useState inside the form component. I also tried adding a new prop in the Togglable component that bind the onClick event of the save button to the onSubmit event of the form, but not work because there is no submit button inside form component.
The solution was much easier than I thought. You only need to wrap the entire children inside a <form> label and render only the content of the inputs. For every children a button will be added and you will manage the form outside the wrapper.
const Togglable = ({ title, children, handleSubmit }) => {
const [visible, setVisible] = useState(false);
const hideWhenVisible = { display: visible ? 'none' : '' };
const showWhenVisible = { display: visible ? '' : 'none' };
return (
<header>
<h2>{title}</h2>
<div style={hideWhenVisible}>
<button onClick={() => setVisible(true)}>Show</button>
</div>
<div style={showWhenVisible}>
<button onClick={() => setVisible(false)}>Close</button>
<form onSubmit={handleSubmit}>
{children}
<button type="submit">Save</button>
</form>
</div>
</header>
);
};

Input field not cleared after using useState with onClick

I have a React app, where I'm using an input field as a searchbar, which upon typing, returns a list of products, and upon clicking any product takes you to that product page. I want to clear the typed text in the searchbar after the redirection to new page has happened but I haven't been able to achieve this yet.
I've tried many methods and went over similar posts, but I don't know what am I doing wrong as text is never cleared.
I'm using Material UI for rendering the list and have imported everything as needed.
Below is my code:
Navbar component (contains searchbar)
const Navbar = () => {
const [text, setText] = useState('');
const [liopen, setLiopen] = useState(true);
const getText = (text) => {
setText(text);
setLiopen(false);
};
const handleClick2 = (e) => {
setText('');
setLiopen(true)
};
return (
<header>
<nav>
<div className="middle">
<div className="nav_searchbar">
<span className="search_icon">
<SearchIcon id="search" />
</span>
<input
type="text"
onChange={(e) => getText(e.target.value)}
name=""
placeholder="Search for products, categories, ..."
id=""
/>
</div>
{text && (
<List className="extrasearch" hidden={liopen}>
{products
.filter((product) =>
product.title.toLowerCase().includes(text.toLowerCase())
)
.map((product) => (
<ListItem>
<NavLink
to={`/getproductsone/${product.id}`}
onClick={(e) => {handleClick2(e)}}
>
{product.title}
</NavLink>
</ListItem>
))}
</List>
)}
</nav>
</div>
</header>
);
};
export default Navbar;
You need to set the value of the input if you want it controlled by the component state.
<input value={text}
type="text"
onChange={(e) => getText(e.target.value)}
name=""
placeholder="Search for products, categories, ..."
id=""
/>

react simple keyboard not able to type in multiple input

I have following code of Calculator.jsx where everything looks fine.The main thing I want to achieve is keyboard to displayed only on input click which is done but the keyboard does not seem to type though the following code looks fine. Are there any other way to show/hide keyboard only on input click as well as make keyboard be able to type. My code for Calculator.jsx is
Calculator.jsx
import React, { useState, useRef, useEffect } from 'react';
import './Calculator.css'
import { Link } from 'react-router-dom';
import Keyboard from "react-simple-keyboard";
import "react-simple-keyboard/build/css/index.css";
const Calculator = () => {
const [inputs, setInputs] = useState({});
const [layoutName, setLayoutName] = useState("default");
const [inputName, setInputName] = useState("default");
const keyboard = useRef();
const [keyboardVisibility, setKeyboardVisibility] = useState(false);
useEffect(() => {
function clickHanlder(e) {
if (
!(e.target.nodeName === "INPUT") &&
!e.target.classList.contains("hg-button") &&
!e.target.classList.contains("hg-row")
) {
setKeyboardVisibility(false);
}
}
window.addEventListener("click", clickHanlder);
return window.removeEventListener("click", clickHanlder, true);
}, []);
const onChangeAll = inputs => {
setInputs({ ...inputs });
console.log("Inputs changed", inputs);
};
const handleShift = () => {
const newLayoutName = layoutName === "default" ? "shift" : "default";
setLayoutName(newLayoutName);
};
const onKeyPress = button => {
console.log("Button pressed", button);
if (button === "{shift}" || button === "{lock}") handleShift();
};
const onChangeInput = event => {
const inputVal = event.target.value;
setInputs({
...inputs,
[inputName]: inputVal
});
keyboard.current.setInput(inputVal);
};
const getInputValue = inputName => {
return inputs[inputName] || "";
};
return (
<div>
<div className="bg">
<div className="deposit">
<div className="header">
<h1>Deposit Calculator</h1>
<div className="form">
<form className="calculator">
<div className="form-group">
<label for="depositAmount">Deposit Amount:</label>
<span className="rupees">Rs</span>
<input className="IInput"
type="text"
name='depositAmount'
placeholder='0'
value={getInputValue("depositAmount")}
onChange={onChangeInput}
onFocus={() => {
setKeyboardVisibility(true);
setInputName("depositAmount")
}}
/>
</div>
<div className="form-group">
<label for="interestRate">Interest Rate:</label>
<input className= "IIinput"
type="text"
name='Interest'
placeholder='0'
value={getInputValue("interestRate")}
onChange={onChangeInput}
onFocus={() => {
setKeyboardVisibility(true);
setInputName("interestRate")
}}
/>
<span className= "percent">%</span>
</div>
<div class="form-group">
<label for="Tenure">Tenure:</label>
<input className="Input"
type='number'
min='1'
max='5'
name='tenure'
placeholder='1 year'
value={getInputValue("tenure")}
onChange={onChangeInput}
onFocus={() => {
setKeyboardVisibility(true);
setInputName("tenure")
}}
/>
</div>
{ keyboardVisibility && (
<Keyboard
keyboardRef={(r) => (keyboard.current = r)}
layoutName={layoutName}
onChange={onChangeAll}
onKeyPress={onKeyPress}
/>
)}
</form>
<button className="calculate">Calculate
</button>
</div>
<div className="given">
<p >
Total Deposit: Rs 0
</p>
<p>
Interest: Rs 0
</p>
<p>
Maturity Amount: Rs 0
</p>
</div>
</div>
</div>
</div>
<Link to="/">
<button className="Back">
<i class="fas fa-angle-double-left"></i>
</button>
</Link>
</div>
);
};
export default Calculator;
You are setting the inputs state by spreading input string from keyboard onChangeAll into an object setInputs({ ...inputs }). If I enter ab it will set as {0: "a", 1:"b"}.
Update the onChange prop in Keyboard to onChangeAll and pass inputName prop with your inputName state value. Read react-simple-keyboard DOCS.
onChangeAll
const onChangeAll = (inputs) => {
console.log("Inputs changed", inputs);
setInputs(inputs);
};
Keyboard
{keyboardVisibility && (
<Keyboard
keyboardRef={(r) => (keyboard.current = r)}
layoutName={layoutName}
onChangeAll={onChangeAll}
onKeyPress={onKeyPress}
inputName={inputName}
/>
)}
CodeSandbox link

How do I add a task in a react to-do app by pressing ENTER key?

I am new to react.js .
I made a simple to-do app to learn CRUD using react.js:
A task is added when I click the '+' button. But I need to add a task when I click the 'ENTER' Key.
What should I do?
Here's A Part Of My Code :
JSX :
function Body() {
const [toDos,setToDos] = useState([])
const [toDo,setToDo] = useState('')
const deleteTodo = idToDelete => setToDos(currentTodos => currentTodos.filter(toDo => toDo.id !== idToDelete))
return (
<div className="bodyoftodo">
<div className="input">
<form onSubmit={toDo} >
<input value={toDo} onChange={(e)=>setToDo(e.target.value)} type="text" placeholder="🖊️ Add item..." />
<i onClick={()=>setToDos([...toDos,{id:Date.now() ,text: toDo, status: false}])} className="fas fa-plus"></i>
</form>
</div>
<div className="todos">
{toDos.map((obj)=>{
return(
<div className="todo">
<div className="left">
<input onChange={(e)=>{
console.log(e.target.checked);
console.log(obj);
setToDos(toDos.filter(obj2=>{
if(obj2.id===obj.id){
obj2.status=e.target.checked
}
You can do it with set a function on onKeyPress event.
handleKeyPress = (event) => {
if(event.key === 'Enter'){
setToDos([...toDos,{id:Date.now() ,text: toDo, status: false}])
setToDo("");
}
}
return(
<div>
<input value={toDo} onChange={(e)=>setToDo(e.target.value)} type="text"
placeholder="🖊️ Add item..." onKeyPress={handleKeyPress} />
</div>
);
}
You could wrap the input and button in a form and include the function to add a task in the onsubmit attribute of the form. That way, the function gets called whether you click the button or press enter.
Like so:
const AddToDo = () => {
// ...state
const [todoText, setTodoText] = useState('');
const handleSubmit = (e) => {
e.preventDefault(); //stop default action of form
//....call add todo function
setTodoText(''); //clear input
}
return (
<form onSubmit={handleSubmit}>
<input type='text' onChange={ ({ target }) => setToDoText(target.value)}>
<button type='submit'>Add ToDo</button>
</form>
)
}
In this case, clicking on the button or pressing enter would trigger the handleSubmit function.
If you are using a form you can do the below by adding on the onSubmit
import { useState } from "react";
export default function App() {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleAddTodo = (e) => {
e.preventDefault()
setTodos([...todos, todo]);
setTodo("");
};
const handleChange = (e) => {
setTodo(e.target.value);
};
return (
<div className="App">
<h2>Todos</h2>
<form onSubmit={handleAddTodo}>
<label>
New Todo
<input value={todo} onChange={handleChange} />
</label>
</form>
<ul>
{todos.map((t, i) => (
<li key={i}>{t}</li>
))}
</ul>
</div>
);
}
Else, you can just use the input and hook up a listener on Key Up / Key Down and if the Key Code is Enter (13).You can trigger to add the Todo
const handleKeyUp = (e) =>{
if(e.keyCode === 13){
handleAddTodo()
}
}
Full Example In Codesandbox https://codesandbox.io/s/ancient-sunset-mr55h?file=/src/App.js:239-325
One option could be to make it form by wrapping your input field and button inside the form tag and then give type="submit" to the button
or you could attach event listner with enter key

How can I get the value of an input field and use it in URL in React

I have to complete this project and I'm having a hard time understanding how the hooks work. I'm completely new to React and I was wondering how I could grab the value of my Input field so I could use it and modify the URL of the API (omdb) I'm working with. ( see ${inputValue} )
Here's the code:
function App() {
const [searchResult, setSearchResult] = useState()
useEffect(() => {
const search = async () => {
const response = await fetch(
`http://www.omdbapi.com/?apikey=aaaaaa&s=${inputValue}`,
)
const data = await response.json()
console.log(data);
if (!searchResult) {
setSearchResult(data);
}
}
search()
})
return (
<div className="App">
<div className="search">
<input type="text" placeholder="Search..." />
<button>Search</button>
</div>
{!searchResult ? (
<p>No results yet</p>
) : (
<div className="search-results">
<div className="chevron">
<ChevronLeft />
</div>
<div className="search-results-list">
{searchResult.Search.map(result => (
<div key={result.imdbID} className="search-item">
<img
src={result.Poster === 'N/A' ? placeholderImg : result.Poster}
alt="poster"
/>
<div className="search-item-data">
<div className="title">{result.Title}</div>
<div className="meta">{`${result.Type} | ${result.Year}`}</div>
</div>
</div>
))}
</div>
<div className="chevron">
<ChevronRight />
</div>
</div>
)}
</div>
)
}
It seemed very simple at first, I thought I could just querySelect the Input tag's value but for some reason that didn't work. I've been searching around but nothing seems to work and very method I tried returns "undefined".
Thanks for the help!!
First, set a state for yout input value:
const [inputVal, setVal] = useState('');
Then, add value and onChange to your input:
<input type="text" placeholder="Search..." value={inputVal} onChange={onInputChange} />
Create a function to set the input value to the state
function onInputChange(evt) {
setVal(e.target.value);
}
Use you input state value as a dependency array of your useEffect
useEffect(() => {
const search = async (value) => {
const response = await fetch(
`http://www.omdbapi.com/?apikey=a461e386&s=${value}`,
);
const data = await response.json();
if (!searchResult) {
setSearchResult(data);
}
}
if (inputVal) {
search(inputVal);
}
}, [inputVal])
You can use define inputValue as a state:
const [inputValue, setInputValue] = useState("");
Assign value to inputValue, and onChange callback you need to set value to inputValue:
<input
type="text"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
/>
Minimal working sample: https://codesandbox.io/s/stoic-bell-j3e2p?file=/src/App.js:180-328

Resources