I was trying to implement focus for the Submit button with Ref. I wanted to omit refering elements by ID.
import React, { useRef } from 'react'
import PropTypes from 'prop-types'
export const LabelComponent = () => {
const createButton = enableCreateButton()
? <button ref={(input) => { this.createLabelBtn = input }} >Submit</button>
: <button disabled ref={(input) => { this.createLabelBtn = input }} >Submit</button>
const createLabelBtn = useRef();
const focusCreateBtn = (e) => {
if ((e.key === 'Enter') && (newLabel.name !== '')) {
this.createLabelBtn.focus();
}
};
return (
<div className='create-label-container'>
<input type='text'
onKeyDown={(e) => { focusCreateBtn(e) }}
/>
{createButton}
</div>
)
}
It gives following error.
Uncaught TypeError: Cannot set property 'createLabelBtn' of undefined
Uncaught TypeError: Cannot set property 'createLabelBtn' of undefined
What could be the issue here.?
Functional components are instanceless, therefore, no this to bind anything to or call upon. Set the ref prop on the button as so ref={createLabelBtn}, and to set the focus you need to access createLabelBtn.current to get at the current value of the ref.
export const LabelComponent = ({ enableCreateButton }) => {
const createLabelBtn = useRef(null);
const focusCreateBtn = e => {
if (e.key === "Enter") {
createLabelBtn.current.focus();
}
};
return (
<div className="create-label-container">
<input type="text" onKeyDown={focusCreateBtn} />
<button
// upon being focused upon, console log proof
onFocus={() => console.log("Submit Focused!")}
disabled={!enableCreateButton}
ref={createLabelBtn}
>
Submit
</button>
</div>
);
};
Try this
import React, { useRef, useState } from "react";
const LabelComponent = () => {
const [name, setName] = useState("");
const createButton = true ? (
<button
ref={input => {
createLabelBtn.current = input;
}}
>
Submit
</button>
) : (
<button
disabled
ref={input => {
createLabelBtn.current = input;
}}
>
Submit
</button>
);
const createLabelBtn = useRef();
const focusCreateBtn = e => {
if (e.key === "Enter" && name !== "") {
createLabelBtn.current.focus();
}
};
return (
<div className="create-label-container">
<input
type="text"`enter code here`
value={name}
onChange={e => {
setName(e.target.value);
}}
onKeyDown={e => {
focusCreateBtn(e);
}}
/>
{createButton}
</div>
);
};
export default LabelComponent;
Related
I Am failing to get the radio button selected when I press the Enter key
I tried this code here :
import { useState, useEffect } from 'react';
const HandleKeypress = () =\> {
const [itWorks, setItWorks] = useState(true)
useEffect(() => {
document.addEventListener('keypress', (e) => {
if (e.code === 'Enter') setItWorks(!itWorks)
e.preventDefault();
})
}, [])
return (
<div>
<p>{itWorks ? 'It works!' : 'It does not'}</p>
<button onClick={() => setItWorks(!itWorks)} >Press me</button>
<input type='radio' aria-selected onKeyPress={() => this.HandleKeypress } />
</div>
)
}
export default HandleKeypress;
You dont have to use onKeyPress() use onClick() instead.
You are using functional component, so, do
<input type='radio' aria-selected onClick={handleClick} />
You cant call the main component which you are working on.
So, the solution for this is
import { useState, useEffect } from 'react';
const HandleKeypress = () => {
const [itWorks, setItWorks] = useState(false)
function handleClick(){
SetItWorks(true)
}
return (
<div>
<p>{itWorks ? 'It works!' : 'It does not'}</p>
<button onClick={() =>
setItWorks(!itWorks)} >Press me</button>
<input type='radio' aria-selected onClick={handleClick} />
</div>
)
}
export default HandleKeypress;
This will work.
Helllo everyone, I have this issue where I am successfully sorting the array state of an object alphabetically using their cities but the problem is that the array that is getting visualized only updates after I search something on the UI.
I tried to look it up but still lost here is the video of what is happening
https://drive.google.com/file/d/17pAwTeo8IZ6mw3dd2pxDxbfY-ToL7cjG/view?usp=sharing
here is the code
full code here
import React, { useState, useEffect } from "react";
import "./body.css";
import Axios from "axios";
import { Button } from "#material-ui/core";
function SearchBar() {
const [filteredData, setFilteredData] = useState([]);
const [search, setSearch] = useState("");
async function getUsers() {
Axios.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
setFilteredData(response.data);
})
.catch((err) => {
console.log(err);
});
}
useEffect(() => {
getUsers();
}, []);
function handleReset() {
getUsers();
setSearch("");
handleClear();
}
const handleClear = () => {
Array.from(document.querySelectorAll("input")).forEach(
(input) => (input.value = "")
);
};
const delItem = (id) => {
setFilteredData(filteredData.filter((e) => e.name.localeCompare(id) !== 0));
};
const sort = () => {
setFilteredData(
filteredData.sort((a, b) => {
return a.address.city.localeCompare(b.address.city);
})
);
// console.log(sorted);
// setFilteredData(sorted);
console.log(filteredData);
};
return (
<div>
<form class="search-bar">
<input
type="input"
name="search"
pattern=".*\S.*"
// requiredw
autoComplete="off"
placeholder="Input Text Here"
onChange={(e) => setSearch(e.target.value)}
></input>
</form>
<Button onClick={handleReset}>Reset</Button>
<Button onClick={sort}>Sort</Button>
<div>
<ul>
{filteredData
.filter((user) => {
var dynamicSearch = search;
if (
user.name.toLowerCase().includes(dynamicSearch.toLowerCase()) ||
user.email
.toLowerCase()
.includes(dynamicSearch.toLowerCase()) ||
user.phone
.toLowerCase()
.includes(dynamicSearch.toLowerCase()) ||
user.address.city
.toLowerCase()
.includes(dynamicSearch.toLowerCase())
) {
return true;
}
})
.map((val, index) => (
<li className="li" key={val.id}>
<p className="list">
{"Name: "}
{val.name} <br />
{"Email: "}
{val.email}
<br />
{"Phone: "}
{val.phone}
<br />
{"City: "}
{val.address.city}
</p>
<button className="delButton" onClick={() => delItem(val.name)}>
x
</button>
</li>
))}
</ul>
</div>
</div>
);
}
export default SearchBar;
Just try with this one:
const sort = () => {
const sortedData = filteredData.sort((a, b) => {
return a.address.city.localeCompare(b.address.city);
});
setFilteredData([...sortedData]);
};
Problem is once you are updating the sorting data in setFilteredData function its not able to observe the changes that needs to be rendered. So always make the copy of the state variables when you are updating the values.
I have a react component
import React from 'react';
import classes from './Input.module.css';
import PlayCircleFilledWhiteIcon from '#material-ui/icons/PlayCircleFilledWhite';
export const Input = ({ trackHandler }) => {
const handleTrack = (e) => {
if(e.key === 'Enter') {
trackHandler(e.target.value);
e.target.value = '';
}
}
return (
<>
<div className = {classes.forma}>
<input
type="text"
maxLength = '30'
placeholder = 'Enter tracker name'
onKeyPress = {e => handleTrack(e)}
className = {classes.inputText}
/>
<PlayCircleFilledWhiteIcon className = {classes.btnSubmit}/>
</div>
</>
)
}
Function trackHandler pass the value from input to another component.
I need to pass this value in two ways: by press key 'Enter' on keyboard or click on button. I've realised first way but I need to create both of them.
Thanks in advance for helping.
You can do something like this. Use useState hook to store the input value and create a common function which will be called on button click and on enter key press.
import React, { useState } from "react";
import "./style.css";
const Input = ({}) => {
const [val, setVal] = useState("");
const handleTrack = () => {
if (val.length !== 0) {
// Do something with value
console.log("got this:", val);
}
};
const handleKeyPress = e => {
if (e.key === "Enter") {
handleTrack();
}
};
return (
<div>
<input
value={val}
onChange={e => {
setVal(e.target.value);
}}
onKeyPress={handleKeyPress}
/>
<button
onClick={() => {
handleTrack();
}}
>
Click
</button>
</div>
);
};
export default function App() {
return (
<div>
<Input />
</div>
);
}
Stackblitz: https://stackblitz.com/edit/react-vane9t
You can use useRef as ref property on input.
const inputRef = useRef(null)
Then you get access to input value something like this:
inputRef.target.value
If this not work for first you should log the inputRef to the console which is the exact property what you need.
I'm new to React and even more new to Next.js
I've got an input where the user search a city by name in a list of all the cities available.
I've read that useSWR could be interesting to use (before that I made the request with axios inside a useEffect).
Once I got the array of cities I do a map to return all the matches with the request (and then I use it to do an autocomplete).
But I get an error like this :
"Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."
If I just fetch the data, it works but if I do the map on the array I get an infinite loop and I don't know why.
my code :
import React, { useState, useEffect } from "react";
import styles from "./searchBar.module.css";
import { useRouter } from "next/router";
import axios from "axios";
import useSWR from "swr";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch, faAngleDown } from "#fortawesome/free-solid-svg-icons";
import installationType from "../public/installation_type_ids.json";
const SearchBar = ({
setSearchSport,
setSearchCity,
searchCity,
searchSport,
setSearchType,
searchType,
setPage,
}) => {
const router = useRouter();
// States for search bar request
const [city, setCity] = useState(searchCity);
const [cityData, setCityData] = useState([]);
const [sport, setSport] = useState(searchSport);
const [title, setTitle] = useState("");
const [type, setType] = useState(searchType);
const [autoComplete, setAutoComplete] = useState([]);
const [displayAutoComplete, setDisplayAutoComplete] = useState(false);
// handle submit button
const handleSubmit = (e) => {
e.preventDefault();
setSearchCity(city);
setSearchSport(sport);
type ? setSearchType(type) : setSearchType("ALL");
setPage(0);
if (router.pathname !== "/points-de-rencontre-sportive")
router.push("/points-de-rencontre-sportive");
};
const url = "https://bouge-api.herokuapp.com/v1.0/city/ids";
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data: result, error } = useSWR(url, fetcher);
if (error) return <h1>Oups ! Une erreur est survenue...</h1>;
if (!result) return <h1>Chargement en cours...</h1>;
const handleTest = (e) => {
setCity(e.target.value);
e.target.value === 0
? setDisplayAutoComplete(false)
: setDisplayAutoComplete(true);
if (result && result.data) {
const dataMapped = result.data.map((city) => {
return { city: city.name, type: "city" };
});
let tab = [];
dataMapped.map((item, i) => {
item.name
if (item.name.toLowerCase().includes(city)) {
tab.push(item);
}
return setAutoComplete(tab);
});
}
};
// autocomplete rendering
const renderAutoComplete = autoComplete.map((elem, index) => {
console.log(elem);
if (index <= 9) {
return (
<div
className={styles.autocompleteDiv}
key={index}
onClick={() => {
if (elem.type === "city") {
setCity(elem.city);
}
if (elem.type === "sport") {
setSport(elem.sport);
}
setDisplayAutoComplete(false);
}}
>
<p>{elem.type === "city" ? elem.city : elem.sport}</p>
</div>
);
} else {
return null;
}
});
return (
<div className={styles.searchBar}>
<form className={styles.form} onSubmit={handleSubmit}>
<div>
<label htmlFor="city">Ville</label>
<input
type="text"
id="city"
placeholder="Où veux-tu jouer?"
value={city}
onChange={handleTest}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="sport">Sport</label>
<input
type="text"
id="sport"
placeholder="Spécifie le sport"
value={sport}
onChange={(e) => {
setSport(e.target.value);
}}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="title">Nom</label>
<input
type="text"
id="title"
placeholder="Recherche par nom"
value={title}
onChange={(e) => {
setTitle(e.target.value);
let tab = [];
installationType.map((item, i) => {
if (item.installation_type.includes(title)) {
tab.push(item);
}
return setAutoComplete(tab);
});
console.log(tab);
}}
autoComplete="off" // disable chrome auto complete
/>
</div>
<div>
<label htmlFor="type">Type</label>
<select
type="text"
id="type"
placeholder="Type de structure"
value={type}
>
<option value="ALL" defaultValue>
Type de structure
</option>
<option value="AS">Association</option>
<option value="PRIV">Privé</option>
<option value="PUB">Public</option>
<option value="EVENT">Activité</option>
</select>
<i>
<FontAwesomeIcon
icon={faAngleDown}
className={styles.selectIcon}
></FontAwesomeIcon>
</i>
</div>
<button>
<i>
<FontAwesomeIcon
icon={faSearch}
className="fa-lg"
></FontAwesomeIcon>
</i>
Rechercher
</button>
</form>
{displayAutoComplete ? (
<div className={styles.searchResults}>{renderAutoComplete}</div>
) : null}
</div>
);
};
export default SearchBar;
After you fetched data, you call setCityData method to update city data, this cause component re-render and run code in your SearchBar component again, so it call setCityData again and then continue re-render => infinite re-render.
I think you should put it into a useEffect:
useEffect(() => {
if (result && result.data) {
const dataMapped = result.data.map((city) => {
return { city: city.name, type: "city" };
});
setCityData(dataMapped)
}
}, [result])
so it will update city data only when result has data
When typing and logging the input e.target.value, I get the default value + the last key stroke, but nothing re-renders. I guess that React doesn't recognize that the state changed, but I'm having a problem finding out the correct way to do this.
This is the code in question:
const [text, setText] = useState(task.text);
console.log(text);
const handleInputChange = (e) => {
setText(e.target.value);
};
const taskInput = (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
);
And the full file:
import React, { useContext, useState } from "react";
import { TaskContext } from "../context/TaskState";
const Task = ({ task }) => {
const { deleteTask } = useContext(TaskContext);
const { changeStatus } = useContext(TaskContext);
const taskText = (
<div
className='task-text'
onClick={() => changeStatus({ ...task, done: !task.done })}
style={task.done ? { textDecoration: "line-through" } : null}
>
{task.text}
</div>
);
const [text, setText] = useState(task.text);
console.log(text);
const handleInputChange = (e) => {
setText(e.target.value);
};
const taskInput = (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
);
const [option, setOption] = useState(taskText);
return (
<div className='task-container'>
<button className='task-edit' onClick={() => setOption(taskInput)}>
edit
</button>
<button className='task-delete' onClick={() => deleteTask(task.id)}>
x
</button>
{option}
</div>
);
};
export default Task;
I'am using global state for the rest of the app and reducers.
I think, onChange in your input might cause this error. Try replacing this:
onChange={handleInputChange}
with this:
onChange={(e) => handleInputChange(e)}
e object might be not passed to your method.
Please try wrapping your taskInput value in useMemo with dependency text as when you store JSX as variable during re-render they are refering to the previous value as they don't know the variable they used have value changed.
import React, { useMemo, useContext, useState } from "react";
const taskInput = useMemo(() => (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
), [text]);
The problem was the way I passed option inside the jsx.
I made the option state a boolean, converted taskText and taskInput to functions and passed option conditionally inside the jsx.
import React, { useContext, useState } from "react";
import { TaskContext } from "../context/TaskState";
const Task = ({ task }) => {
const { deleteTask } = useContext(TaskContext);
const { changeStatus } = useContext(TaskContext);
const taskText = () => {
return (
<div
className='task-text'
onClick={() => changeStatus({ ...task, done: !task.done })}
style={task.done ? { textDecoration: "line-through" } : null}
>
{task.text}
</div>
);
};
const [text, setText] = useState(task.text);
console.log(text);
const handleInputChange = (e) => {
setText(e.target.value);
};
const taskInput = () => {
return (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
);
};
const [option, setOption] = useState(true);
return (
<div className='task-container'>
<button className='task-edit' onClick={() => setOption(!option)}>
edit
</button>
<button className='task-delete' onClick={() => deleteTask(task.id)}>
x
</button>
{option ? taskText() : taskInput()}
</div>
);
};
export default Task;