I have been searching SO for a while so this should not be a duplicate. But, I am trying to trigger a link click when the enter key is pressed.
This is what I am working with:
handleKeyPress(target) {
if(target.charCode==13){
alert('Enter clicked!!!');
}
}
Search input:
<SearchBox
type="text"
value={value}
onChange={e => this.onChange(e)}
className="search-box"
placeholder="Search"
aria-label="search"
aria-describedby="basic-addon2"
onKeyPress={this.handleKeyPress}
/>
<div>
<Link to={`/search?q=${value}`} className="btn submit-button"><i className="fa fa-search"></i></Link>
</div>
Using React Instant Search I want to submit the inputs 'value' when enter is clicked. Currently I can only submit the value when I physically click on:
<div>
<Link to={`/search?q=${value}`} className="btn submit-button"><i className="fa fa-search"></i></Link>
</div>
I can get the link to fire. But, how can I get the same functionality as the link click when I press enter too? Any suggestions on how to link to the search value via KeyPress?
If you already react-router-dom you can use the following:
import { withRouter } from 'react-router-dom'
class *ClassName* extends React.Component {
..
handleKeyPress(target, value) {
const { history } = this.props;
if(target.charCode==13){
history.push(`/search?q=${value}`);
}
}
..
render() {
return (
..
<SearchBox
value={value}
..
onKeyPress={e => this.handleKeyPress(e, value)}
/>
)
}
..
}
export default withRouter(*ClassName*);
Important here ist that you use the withRouter(..) export to get the history from your props.
According to react-statics documentation they recommend installing Reach Router for dynamic routing. To navigate programmatically with Reach Router you should be able to import navigate.
import { navigate } from "#reach/router"
...
handleKeyPress(target) {
// I'm guessing you have value stored in state
const { value } = this.state;
if(target.charCode==13){
navigate(`/search?q=${value}`);
}
}
Option 2
Honestly that seems like a lot of work when you could probably just do it with javascript.
handleKeyPress(target) {
// I'm guessing you have value stored in state
const { value } = this.state;
if(target.charCode==13){
const { href } = window.location;
window.location.href = `${href}/search?q=${value}`;
}
}
Related
I have an array with several "question" elements, each of them with a structure similar to this:
<><div className="row"><div className="col-12 col-md-9 col-lg-10 col-xl-10 col-xxl-10"><span>Question 1</span></div><div className="col-3 col-md-1 col-lg-1 col-xl-1"><div className="form-check"><input id="formCheck-1" className="form-check-input" type="checkbox" /><label className="form-check-label" for="formCheck-1">Yes</label></div></div><div className="col-3 col-md-1 col-lg-1 col-xl-1"><div className="form-check"><input id="formCheck-2" className="form-check-input" type="checkbox" /><label className="form-check-label" for="formCheck-2">No</label></div></div></div></>,
In order to give each element a bit of keyed structure, I store each array element in a helper component. The HTML of the questions is simply stored in "element". Simple:
const ElementoPaginacion = ({element}) =>{
return(
element
)
}
Since there can be many of these elements in this array, they are displayed with pagination. The displayed page is calculated (apparently correctly, using a simple calculation). The code snippet that calculates and displays it is as follows:
<>
{
//Calculate init index (it depends ont the current page) to show the questions, and the number of elements to show (its rangePages)
fullList.slice(currentPage * rangePages, (currentPage * rangePages) + rangePages).map((current) => (
<React.Fragment key={current.key}>
{current}
</React.Fragment>
))
}
What happens is that, when a change is made to that HTML by the user (for example, checking a checkbox), when changing the page, that change is NOT saved, if it is redrawed (for example, changing the page and returning to the same page afterwards). I am attaching images to see how it works:
We can see how we make changes to the questions on page 0, we change to page 1, and when we return to page 0 again, the changes (check ckeckboxes) have not been saved.
That could be happening?
------------- EDIT -------------
Okay. Right now, the idea is to save changes to any portion of HTML that is passed to us, be it a question with checkbox responses, or a radiobutton.
The problem: if we don't know what content is going to pass, what we have to save is all the content in html. Now, how can we save any HTML content that has been modified? I've tried creating this helper component, which wraps the HTML content passed to it inside a "div", but when clicked, how can I retrieve the new HTML content to reassign (ie the "newData" parameter)?
const ElementoPaginacion = ({element}) =>{
const [content, saveElement] = useState(element);
const saveData = (newData) =>{
saveElement(newData);
}
return(
<div onChange={saveData}>
{element}
</div>
)
}
Are you using a backend database? If not, the changes are stored in memory and will be lost each time you change the page.
You can use the useState hook to prevent the state of the forms from being lost each time the element is dismounted. Then the user can submit the state in its entirety after answering the questions.
Example:
import { useState } from 'react';
import { Button } from '#mui/material';
import { Checkbox } from '#mui/material';
function App() {
const [checkBoxIsMounted, setCheckBoxIsMounted] = useState(false);
const handleClick = () => {
setCheckBoxIsMounted(!checkBoxIsMounted)
}
return (
<>
<Button onClick={handleClick}>Mount Checkbox</Button>
{checkBoxIsMounted && <Checkbox />}
</>
);
}
export default App;
Note, I'm using MUI instead of Bootstrap, but the underlying principle is the same.
The above code snippet produces the following behavior:
If we add state to the checkbox, React will maintain the state in memory even after the component is dismounted:
import { useState } from 'react';
import { Button } from '#mui/material';
import { Checkbox } from '#mui/material';
function App() {
const [checkBoxIsMounted, setCheckBoxIsMounted] = useState(false);
const [checked, setChecked] = useState(false);
const handleClick = () => {
setCheckBoxIsMounted(!checkBoxIsMounted)
}
const handleChange = () => {
setChecked(!checked)
}
return (
<>
<Button onClick={handleClick}>Mount Checkbox</Button>
{checkBoxIsMounted && <Checkbox onChange={handleChange} checked={checked} />}
</>
);
}
export default App;
This modified snippet produces this behavior:
This is a difficult question because there are so many moving parts, but allow me to attempt to explain the scenario before I start shoving code in everyone's face.
My goal is to allow managers to have a screen where all their drivers are displayed. They will have minimal information displayed and an edit button. If the user clicks the edit button they will stay on the same page. There is a useState, const [driverSelected, setDriverSelected] = useState("") that once an edit button is clicked, will call setDriverSelected to be the driver, not just the id. So once an edit button is clicked, an actual new value for driverSelected would look like this...
{id: 'a049c673-da36-48e6-8fbd-32ab925b6178', role: 'USER', firstname: 'STEVEN', lastname: 'MONROE', email: 'TQRGJGNFQVIO', …}
deleted: false
email: "TQRGJGNFQVIO"
firstname: "STEVEN"
id: "a049c673-da36-48e6-8fbd-32ab925b6178"
lastname: "MONROE"
locked: false
phoneNumber: "null"
profilePick: null
role: "USER"
__typename: "Driver"
[[Prototype]]: Object
Based on this, the same page will change from displaying all the drivers to just the one selected, and input fields to change his/her attributes. This all works properly.
From here, you hit submit and it sends a mutation over to the database. This also works. Then, a query is automatically launched to send the user back the new driver data. This also also works. Where everything breaks is once the mutations/queries are run, I also run setDriverSelected({id: -1}) which should render the drivers list again, but nothing appears at all.
I thought it may be an issue with the data flow, but it isn't. I have console.log statements everywhere along the way from the mutation to the re-render, and at every point the console.log statements return exactly what they're supposed to. No errors in the console, no failed fetches or anything like that from the network. I just literally get nothing. I've even tried replacing all the data with static information, still nothing.
The code is all spread out too across about 7 files since I was trying to compartmentalize as much as possible while using React, so bare with the ugly mess of code files you're about to see.
This is the first page in question, the one that is in charge of either rendering the list OR the driver's fields when chosen.
import React from "react";
import { useState } from "react";
import { useRecoilState } from "recoil";
import { userState } from "../../recoil/atoms";
import SideMenu from "../../components/Home/SideMenu/SideMenu";
import DriverCard from "./DriverCard";
import EditDriver from "./EditDriver";
import "../../styles/EditDrivers/EditDriversLanding.css"
const EditDriversLanding = () => {
// Recoil Data
const rawUser = useRecoilState(userState)
console.log(rawUser)
const user = rawUser[0]
// Local states
const [getSearch, setSearch] = useState("")
const [driverSelected, setDriverSelected] = useState({id: -1})
// Based off of what you type in the search bar, it will filter out invalid employees
const filterDriversList = (list) => {
let filteredList = []
if (getSearch == ""){
return list
}
else{
let filterString = getSearch.toUpperCase()
list.forEach( (driver) => {
if (driver.firstname.includes(filterString) || driver.lastname.includes(filterString)){
filteredList.push(driver)
}
})
return filteredList
}
}
// Takes the list of drivers and renders them all into a list of components
const renderDriverCards = (list) => {
let i = 0
console.log("Okay.... like dude you're RIGHT here, RENDER")
console.log(list)
return list.map( (driver)=> {
i++
if (i == 1){
console.log(driver)
console.log("WHY WONT YOU WORK???")
}
return (<DriverCard driver={driver} key={i} setDriverSelected={setDriverSelected} />)
})
}
const renderListOrEditScreen = () => {
// No Driver selected
if (driverSelected.id == -1){
console.log("dude.... render!!!")
return(
<div className="overlay">
<div className="edit-landing-container">
<SideMenu />
<div className="edit-landing-search-bar">
<input type="text" onChange={(event) => setSearch(event.target.value)} />
</div>
<div className="edit-landing-drivers-list">
{renderDriverCards(filterDriversList(user.drivers))}
</div>
</div>
</div>
)
}
// Driver Selected
else{
return(
<div className="overlay">
<div className="edit-landing-container">
<SideMenu />
<div>
<EditDriver driverData={driverSelected} setDriverSelected={setDriverSelected}/>
</div>
</div>
</div>
)
}
}
if (driverSelected.id == -1){
console.log("should be rendering...")
}
return (
<div>
{renderListOrEditScreen()}
</div>
)
}
export default EditDriversLanding
Its worth mentioning again that this file above works perfectly the first time it is rendered, but after a driver is edited, NOTHING renders-- not a single <div>
Here is the file for the <DriverCard />
import React from "react";
import "../../styles/EditDrivers/EditDriversLanding.css"
const DriverCard = ({driver, setDriverSelected}) => {
console.log(driver)
console.log("dude just work bro")
return(
<div className="edit-drivers-driver-card">
<div>
Image
</div>
<div>
<div>{driver.firstname} {driver.lastname}</div>
<div>{driver.email}</div>
<div>{driver.phoneNumber}</div>
<div className="edit-driver-driver-card-edit-button" onClick={() =>setDriverSelected(driver)}>Edit</div>
</div>
</div>
)
}
export default DriverCard
And finally, here is the EditDriver page which is where the mutation and re-query take place. Notice here you'll see a <div> that on press will also setDriverSelected({id: -1}) and THAT one decides to work-- just the submitting changes kills everything.
import React from "react";
import { useState } from "react";
import DriverField from "../../components/EditDrivers/DriverField";
import SubmitEdits from "./submitEdits";
import "../../styles/EditDrivers/EditDriversLanding.css"
const EditDriver = ({driverData, setDriverSelected}) => {
const [driver, setDriver] = useState(driverData)
const handleInput = (event) => {
const input = { ...driver };
input[event.target.id] = event.target.value;
setDriver(input);
};
return(
<div className="edit-driver-editting-page">
<div onClick={() => setDriverSelected({id: -1})} className="edit-driver-editting-page-exit-button">
Return to Driver Selection
</div>
<div>
<h2>Edit {driverData.firstname} {driverData.lastname}</h2>
</div>
<div>
<div>
<DriverField currentValue={driver.firstname} name="firstname" handleInput={handleInput} />
</div>
<div>
<DriverField currentValue={driver.lastname} name="lastname" handleInput={handleInput} />
</div>
<div>
<DriverField currentValue={driver.email} name="email" handleInput={handleInput} />
</div>
<div>
<DriverField currentValue={driver.phoneNumber} name="phoneNumber" handleInput={handleInput} />
</div>
<SubmitEdits driver={driver} setDriverSelected={setDriverSelected}/>
</div>
</div>
)
}
export default EditDriver
It's hard to say why nothing at all is rendering - but it looks like your landing page component is more complex than it needs to be. It's not often that you need to have functions which render content (e.g. renderListOrEditScreen and renderDriverCards) - often that's a sign that you should break those functions out into their own components.
So, I'd suggest you start by splitting that up into smaller components that do less work. It looks like one of the functions of that page is to act as the "search" page - you could split that up using something like this:
const useFilteredDriversList = (drivers, search) => {
return useMemo(() => {
if (!search) return drivers;
const searchUpper = search.toUpperCase();
return drivers.filter(driver =>
driver.firstName.includes(searchUpper) ||
driver.lastName.includes(searchUpper)
);
}, [drivers, search]);
}
const DriverSearch = ({ onDriverSelected }) => {
const [user] = useRecoilState(userState);
const [search, setSearch] = useState("");
const filteredDrivers = useFilteredDriversList(user.drivers, search);
const handleSearchChange = (event) => setSearch(event.target.value);
return (
<>
<div className="edit-landing-search-bar">
<input type="text" onChange={handleSearchChange} />
</div>
<div className="edit-landing-drivers-list">
{filteredDrivers.map(driver => (
<DriverCard
key={driver.id}
driver={driver}
setDriverSelected={onDriverSelected}
/>
))}
</div>
</>
);
}
Note here I've also split out the filtering code from the component - having it inside the component means you're redefining the filter function every time the component renders, which is unnecessary.
OK; now that the search page has been split out, you can just have a landing page component which either shows the search component or the edit component, depending on if a driver has been selected or not. One other thing that I'd do is create an explicit handler for the case of "cancelling" the edit, and have that live in the landing page. The edit page shouldn't have knowledge of how to "cancel" editing (i.e. setting the driver to { id: -1 }) - that's not its responsibility. It should just tell the parent component that it's finished, and let the parent component worry about how to handle that.
Finally, I'd use either null or undefined to represent "no driver selected" rather than a magic object. So, something like this might work:
const DriversPage = () => {
const [driver, setDriver] = useState(undefined);
const handleUnselectDriver = () => setDriver(undefined);
return (
<div className="overlay">
<div className="edit-landing-container">
<SideMenu />
{driver && (
<EditDriver
driverData={driver}
onEditComplete={handleUnselectDriver}
/>
)}
{!driver && (
<DriverSearch onDriverSelected={setDriver} />
)}
</div>
</div>
);
}
Im making a little note taking application. When the user writes their title and note and clicks submit the note get placed on the page... I want to be able to edit the note. When the edit button is clicked, a modal pops up and I want the users title and note to be inside the input boxes in the modal.
Here is an image for a better understanding.
[1]: https://i.stack.imgur.com/9cFSh.png
I want the title and the note to be inside the input boxes, allowing the user to edit them. Below is my modal component, and the function I wrote below is working perfectly but its not "editing" the original title or note, its basically just making a new one.
Any idea how I can get the title and note in the modal input boxes and just directly modify them as needed? Thanks in advance!
import React, { useState } from "react";
export default function Modal({
title,
note,
setCompletedNote,
FullNote,
setIsModalShown,
...props
}) {
const [newTitle, setNewTitle] = useState("");
const [newNote, setNewNote] = useState("");
function editNoteCompleted(id, newTitle, newNote, e) {
setCompletedNote((prevState) =>
prevState.map((n) => {
if (n.id === id) {
return { ...n, title: newTitle, note: newNote };
}
return n;
})
);
setIsModalShown(false);
}
return (
<div className="modal__container">
<div className="modal__note-information">
<p>Edit Note:</p>
<input
type="text"
name="newTitle"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
/>
<br />
<input
type="text"
value={newNote}
name="newNote"
onChange={(e) => setNewNote(e.target.value)}
/>
<div className="modal__button-container">
<button
className="modal__ok-button"
onClick={() => editNoteCompleted(FullNote.id, newTitle, newNote)}
>
Ok
</button>
<button className="modal__cancel-button">Cancel</button>
</div>
</div>
</div>
);
}
If the title and note of the selected component is passed as props . Then you just need to have the initialValue of your state to be the title and note prop.
export default function Modal({
title,
note,
setCompletedNote,
FullNote,
setIsModalShown,
...props
}) {
const [newTitle, setNewTitle] = useState(title || '');
const [newNote, setNewNote] = useState(note || '');
On my site, the <ArticleList> is supposed to update when one navigates between columns. This works when you go from the home page to a column, or from an article to a column. But if you go from column to column, it doesn't work. The page doesn't update at all, but the url changes. The links to each column stay the same, as they are part of the <Layout> component, which every page has.
Edit
I figured out now that I can just use <a> and omit <Link> entirely, but this would slow down the page navigation.
Edit 2
This is part of my <Layout> component where I render the links to the columns:
<nav className={layout.columnContainer}>
{columns.map(({ id, name }) =>
this.props.currentColumn ? (
<a key={id} href={`/columns/${name}`}>
{name}
</a>
) : (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}>
<a>{name}</a>
</Link>
),
)}
</nav>
Edit 3
My minimal reproducible example is on GitHub, and I get the same unexpected results.
Edit 4
I found that the reason it wasn't working was I implemented a search bar that put the children prop in a state and modified this.
Constructor:
constructor(props) {
super(props);
this.searchArticlesKeyType = this.searchArticlesKeyType.bind(this);
this.state = {displayedMain: props.children};
}
Inside the render method are the column links (nav) and the problematic search input element.
<nav className={layout.columnContainer}>
{
columns.map(({id, name}) => (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}><a>{name}</a></Link>
))
}
</nav>
<div className={layout.search}>
<input type="search" name="q" onKeyUp={this.searchArticlesKeyType} />
</div>
async searchArticlesKeyType(e) {
// Some code
this.setState({
displayedMain: <ArticleList articles={JSON.stringify(filteredArticles)}/>
// More code
});
}
I think your main issue here is the way you're implementing the search feature, you don't want to store components in state instead you need to pass the search text to the articlelist component and do the filtering there.
There are several ways to implement communication between 2 unrelated components, it could be via context, redux, or even make a portal in the layout to render the seach input from the column component, but in this case I think the best option is to store the search text in the url:
First make the input event update the url using next/router, your layout will look like this:
import { useRouter } from 'next/router'
...
function Layout(props) {
const {columns} = props;
const { push, asPath, query } = useRouter()
const searchArticlesKeyType = (e) => {
const q = e.target.value;
const [url] = asPath.split('?');
push(`${url}?q=${q}`, undefined, { shallow: true });
}
return (
<div>
...
<div>
<input type="search" name="q" defaultValue={query.q} onKeyUp={searchArticlesKeyType} />
</div>
...
</div>
)
}
And then you do the filtering in articlelist component
import Link from "next/link";
import { useRouter } from 'next/router'
export default function ArticleList(props) {
const { query } = useRouter();
const q = query.q || "";
const filteredArticles = props.articles.filter(
(item) => item.title.includes(q) || item.body.includes(q)
);
return (
<ul className="grid">
{filteredArticles.map((item) => (
<div key={item.id}>
<Link
key={item.id}
href="/articles/[title]"
as={`/articles/${item.title}`}
>
<a>
<p>
<strong>{item.title}</strong>
</p>
<p>{item.body.substring(0, 100)}</p>
</a>
</Link>
</div>
))}
</ul>
);
}
I am building a huge form which is made of atleast 50 inputs.
I have wrote a function in the form component that will save the value of the input to the form state:
PARENT FUNCTION
saveToState(details) {
const { company } = this.state;
company[details.part][details.element] = details.value;
this.setState({ company });
}
PASSING TO CHILD COMPONENT (INPUT)
<FieldInput
label="Name (as shown) *"
part="information"
element="displayName"
saveToState={this.saveToState}
/>
Here is the Input component:
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onChange={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}
In result whenever I type something in the input It's taking it 200-300ms to really display what I wrote in the input, the state is getting updates instantly but whenever I type a character I set the new state of the parent form and update it which updates the whole component. The only way i found around it is to use saveToState within the parent component without passing it down. but that would require 1000's of line of code, Is there any way around this? Thanks!
There are alot of ways you can solve this problem. The easiest one and the fastest one is to use onBlur instead of onChange that way setState will happen not when you key pressing in the input but when the input loses focus.
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onBlur={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}