How can I update the props.data from a child component? - javascript

I am trying to display a range from an array that is being passed into a child.
My current Parent component is as follows:
import data from './data.json'
return (
<Cell symbol={data.symbol} number={data.number} />
)
And that data is being passed into my Child component:
const [updatedSymbols, setUpdatedSymbols] = useState()
useEffect(() =>
if(props.number === 1 || props.number === 2 || props.number === 3 ){
setUpdatedSymbols(props.number)
console.log("updated: " + props.number)
}
}, [props.symbol])
return (
<div class="cell">
{updatedSymbols}
</div>
)
QUESTION: If you look at the useEffect, you will notice that within the if-statement, I am selecting the numbers I want and simply passing them into the useState, "setUpdatedSymbols".
My problem is that there are too many numbers that I need to select, about 100, how can I push them all into updatedSymbols without using || ?

whatever the numbers list you want to check before you insert into your state you can collectively do this
const [updatedSymbols, setUpdatedSymbols] = useState()
const range = (from, to ) => {
var collection = [];
for(let i = from; i<=to ; i++) {
collection.push(i);
}
return collection;
}
useEffect(() =>
if(range(1,100).includes(props.number) ){
setUpdatedSymbols(props.number)
console.log("updated: " + props.number)
}
}, [props.symbol])
return (
<div class="cell">
{updatedSymbols}
</div>
)
// this is magical trick
// [1,2,3,4,5].includes(props.number); //if props.number happens to be in the array then it i'll give true

Related

Too many re-renders useState React js

I want to display objects from the array one by one by clicking on the card.
To do this, I made a check for array length (props.tasks.length), and if it is more than 1, then the array is shuffled and then I want to display these objects one by one on click.
But after a click, a new array is generated each time and often the objects are repeated 2-4 times.
But I get an error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
function App(props) {
let [random, setRandom] = useState({});
let [newArr, setNewArr] = useState(props.tasks.sort(() => Math.random() - 0.5));
let i = 0;
random = newArr[i]
setRandom(newArr[i])
const randomCard =()=> {
console.log(random);
console.log(i);
console.log(newArr[0],newArr[1],newArr[2],newArr[3], newArr[4], newArr[5]);
console.log(newArr[i]);
if (i <= newArr.length) {
i++
} else {
newArr = props.tasks.sort(() => Math.random() - 0.5);
console.log('clean');
i=0
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{random.name}</p>
<p>{random.translate}</p>
<p>{random.note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;
The main culprit in your code example is
setRandom(newArr[i])
Using a hook's set method should only be done via an action or wrapped in a useEffect for side effects. In your code, setNewArr is called on every rerender and will cause the component to rerender... causing the infinite loop of rerenders.
You also don't need to store the selected element from your array in a state, you're essentially doing this by just storying the index in the use state.
Your solution should look something like this.
Also you want to reset i when it's < newArr and not <= newArr because if your array is of length "2" and i is "2" then you're setting i to "3" which doesn't point to any element in your list.
const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
function App(props) {
// Default empty list
const [newArr, setNewArr] = useState([]);
// Sets the default index to 0
const [i, setI] = useState(0);
// We don't want to call shuffle on every rerender, so only setNewArr
// Once when the component is mounted.
useEffect(() => {
setNewArr(shuffle(props.tasks));
}, []);
const randomCard =()=> {
if (i < newArr.length) {
setI(i + 1);
} else {
setNewArr(shuffle(props.tasks));
setI(0);
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{newArr[i].name}</p>
<p>{newArr[i].translate}</p>
<p>{newArr[i].note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;

react on change check if equal to array of object

i have input and array of object
i need when i type it will display the object. "airplaneCompany" is the object property that i need to compare
i was doing only if the input is equal to the "airplaneCompany" it will return it by the filter method
but i need for evrey char it will check it and if the object start with "a" it will show this object
const [txtInp, setTxtInp] = useState("");
const showFlight = users.filter((user) => {
return user.airplaneCompany == txtInp;
});
{showFlight.map((user, index) => {
const { id, airplaneCompany, passenger } = user;
return (
<div className="flightContainer" key={index}>
<div>{id}</div>
<div>{airplaneCompany}</div>
<div>{passenger}</div>
</div>
);
})}
You can use #Patrick answer, but JavaScript has its own startsWith function you can use.
Also, consider wrapping the filter with the useMemo hook to run it only when the input changes and not on every render.
const showFlight = useMemo(() => {
return users.filter((user) => {
return user.airplaneCompany == txtInp;
});
}, [txtInp]);
I think you can use your .filter function to check if the airplaneCompany starts with the user input?
Something like
return user.airplaneCompany.indexOf(txtInp) === 0;
just use regex. just place input value like /^airplaneCompany$/
const wrongInputText = 'q'
const rightInputText = 'airplaneCompany'
console.log('wrong', 'return value=', /^airplaneCompany$/.test(wrongInputText))
console.log('right', 'return value=',/^airplaneCompany$/.test(rightInputText))

Remove element from useState array by index

SOLUTION: Update the key value for the input element to refresh the default value => content of the input element. Deleting an element from the array DID work. Thanks for your help!
src: https://thewebdev.info/2022/05/12/how-to-fix-react-input-defaultvalue-doesnt-update-with-state-with-javascript/#:~:text=state%20with%20JavaScript%3F-,To%20fix%20React%20input%20defaultValue%20doesn't%20update%20with%20state,default%20value%20of%20the%20input.
I got an useState array in my code which represents a lisst of students:
const [students, setStudents] = useState([""]);
This array gets mapped to student elements:
{students.map((student, index) => <Student setStudents={setStudents} students={students} id={index} key={index} content={student} />)} I also got an AddStudent element which adds students to the array.
function AddStudent(props) {
const {setStudents} = props;
return (
<button className="change-student add-student" onClick={() => {
setStudents((students) => [...students, ""])
}}>
+
</button>
);
}
The RemoveStudent component is supposed to remove a student by its index in the array. I've tried many different ways but none worked correctly. How can I get it to work? Here is my code:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button className="change-student remove-student" onClick={() => {
let data = students;
if(id > -1) {
data.splice(id, 1);
}
console.log(data)
// setStudents(data)
// alternative:
// setStudents(students.filter(index => index !== id)); // removes the last element in the list
// doesn't work properly
}}>
-
</button>
)
}
Thanks for your help!
2 things should be noted here:
While updating react state arrays, use methods that return a new array (map, filter, slice, concat),
rather than ones that modify the existing array (splice, push, pop, sort).
While updating React state using its previous value, the callback argument should be used for the state setter. Otherwise you may get stale values. (See React docs).
if(id > -1) {
setStudents(students=> students.filter((s,i)=>(i != id)))
}
Consult this article, for a complete reference about how to update React state arrays.
You need to copy the students array first and then try removing the student by index. I assume by id you mean index at which to remove the student. Then you can try something like:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button
className="change-student remove-student"
onClick={() => {
if(id > -1) {
const data = [...students]; // making a copy
data.splice(id, 1); // removing at index id
console.log(data)
setStudents(data)
}
}}
>
-
</button>
)
}
With array.filter() you have a mistake in how you pass callback to filter() method. Please try the following:
setStudents(students.filter((,index) => index !== id));
Notice the index is second param of the callback so I used a , before index.
After #Irfanullah Jan 's answer you should make sure how you show the student.
Here is the simple example:
const [students, setStudents] = useState([1, 2, 3]);
return (
<div>
{students.map((student, index) => {
return <div>{student}</div>; // show the value not the index
})}
<button
onClick={() => {
let id = 1;
const copy = [...students];
copy.splice(id, 1)
console.log(copy)
setStudents(copy);
}}
>
-
</button>
</div>
);
The code above will delete the student of "index==1"

Matching multiple substrings to a string

I have an application where the user types in text, then this text is sent to the server and it returns an array of words that contain this text.
But as you can see the problem starts when there are more then 1 match. Here's my current code:
state.input !== '' && vocabularyItems && (vocabularyItems.map((vocabularyItem, index) => {
const regex = new RegExp(input, 'gi');
const results = vocabularyItem.matchAll(regex);
const tmp = [];
console.log(vocabularyItem);
for (const match of results) {
console.log(match);
let currentLoop = vocabularyItem.slice(0, match.index);
currentLoop += '<strong className="tt-highlight">';
currentLoop += vocabularyItem.slice(match.index, match.index + input.length);
currentLoop += '</strong>';
currentLoop += vocabularyItem.slice(match.index + input.length, vocabularyItem.length);
tmp.push(currentLoop);
}
console.table(tmp);
return (
<div
id={index}
className={`override-strong tt-suggestion tt-selectable ${cursor === index && 'tt-cursor'}`}
onMouseDown={handleClick}
key={index}
dangerouslySetInnerHTML={{ __html: tmp }}
/>
);
}))
and here are some examples in HTML code
1.
<strong className="tt-highlight">En</strong>kelkind
2.
<strong className="tt-highlight">En</strong>gagement
Engagem<strong className="tt-highlight">en</strong>t
as you can see, it works when there is only one occurence, but duplicates the word when more then one match is present. How can I end up with just something like
<strong>en</strong>gagem<strong>en</strong>t?
engagement?
I forgot to add that I need the case preserved
First off, I would recommend to use, something, like:
const results = vocabularyItems.filter(word => word.toLowerCase().includes(input.toLowerCase()))
for case insensitive vocabulary lookup.
Next, I would highlight the match in a bit different way. I'd split suggested option into parts (matching search input and non-matching ones) , then style those respectively:
const parts = suggestion.split(new RegExp(`(?=${match})|(?<=${match})`, 'gi'))
...
parts.map((part,key) => {
const style = part.toLowerCase() == match.toLowerCase() ? 'bold' : 'normal'
return <span style={{fontWeight:style}} {...{key}}>{part}</span>
})
I think it's safe enough to assume that you build autocomplete search input, thus you might find of use the quick demo below (excluding all the styling):
//dependencies
const { render } = ReactDOM,
{ useState } = React
//vocabulary
const vocabulary = ['engagement', 'Bentley', 'English', 'seven', 'Engagement']
//suggested option component
const SuggestedOption = ({suggestion, match}) => {
const parts = suggestion.split(new RegExp(`(?=${match})|(?<=${match})`, 'gi'))
return (
<div>
{
parts.map((part,key) => {
const style = part.toLowerCase() == match.toLowerCase() ? 'bold' : 'normal'
return <span style={{fontWeight:style}} {...{key}}>{part}</span>
})
}
</div>
)
}
//autocomplete component
const SearchBar = () => {
const [suggestions, setSuggestions] = useState([]),
[inputValue, setInputValue] = useState(''),
onInput = input => {
setInputValue(input)
setSuggestions(vocabulary.filter(word => input.length && word.toLowerCase().includes(input.toLowerCase())))
}
return (
<div>
<input onKeyUp={e => onInput(e.target.value)} />
<div >
{
suggestions.map((suggestion,key) => <SuggestedOption {...{key,suggestion,match:inputValue}} />)
}
</div>
</div>
)
}
render(
<SearchBar />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Here's a way to do it with string.replace:
const list = [
'end',
'England',
'engagement'
]
const boldify = (search) => {
return (string) =>
string.replace(new RegExp(search, 'gi'), '<strong>$&</strong>')
}
document.body.innerHTML = list.map(boldify('en')).join('<br>')
EDIT: After some times thinking, and seeing the accepted answer, I wanted to push the vanilla JS version to have something more complete. And moreover, the React version seemed laggy to me, this one is faster!
Faster because:
it uses vanilla JavaScript
it uses correct dom manipulation (no innerHTML)
'use strict'
const list = [
'end',
'England',
'engagement',
'Ken Bogard',
'Venom',
'Engagement'
]
const boldify = (search) => {
return (string) => {
const div = document.createElement('div')
const parts = string.split(new RegExp(`(?=${search})|(?<=${search})`, 'gi'))
div.append(
...parts.map(part => {
if (part.toUpperCase() !== search.toUpperCase()) return part
const strong = document.createElement('strong')
strong.innerText = part
return strong
})
)
return div
}
}
const contains = (search) => {
search = search.toUpperCase()
return (string) => string.toUpperCase().indexOf(search) >= 0
}
const refreshSuggestions = () => {
// Fast removal of children.
while (suggestions.firstChild) suggestions.removeChild(suggestions.firstChild)
// nothing to do
if (searchInput.value.length == 0) return
const newSuggestions =
list.filter(contains(searchInput.value))
.map(boldify(searchInput.value))
suggestions.append(...newSuggestions)
}
searchInput.addEventListener('input', refreshSuggestions)
<input id="searchInput" list="suggestions">
<div id="suggestions">
</div>
EDIT - Yevgen's answer is much nicer than this one.
A simple loop can achieve this if i understand correctly what you want:
var array = ["end","engagement","Engagement","england","enough","not this","or this"];
function filterArray(array, id) {
var returnArray = [];
for (var i = 0; i < array.length; i++) {
value = array[i];
if (value.includes(id)) {
returnArray.push(value);
}
}
return returnArray;
}
var filteredArray = filterArray(array,"en");
console.log(filteredArray);
If you wanted to ensure duplicates (where case on Engagement causes a duplicate) you could set the string to lowercase before pushing to the array, and check the array for the existence of the string before pushing again.

How to search in a json array, and return the result as I type in the input

So far I can only search an element of the array if I type the exact name present in my api, in this my api has an array with 20 positions and if I type exactly the name of the element I search it returns an array with 19 positions with undefined and 1 position with the array found, what I want to do and search while I type instead of searching only when I type the full name.
After my search I try to change the state of a component so that it is rendered only with the value fetched, but this does not happen, if anyone knows I am very grateful.
updated code
import data from "../sample_data/recipes.json";
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchString: []
};
}
componentDidMount() {
this.setState({ searchString: data.results })
}
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var indexes = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) !== -1;
})
this.setState({ searchString : indexes });
}
render() {
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={this.state.searchString} />}
</div>
</div>
</div>
);
}
}
export default App;
I suppose that you want some kind of filtering while you're typing:
can you try this ? :
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var filteredItems = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) === -1;
})
this.setState({ searchString : filteredItems });
}
Explanation :
What is requested is to not display items that contains typed letters, to do that, you can use filter items with filter method and return only the items which doesn't have the typed letters in the title (using the indexOf method).
move setState outside map.
replace map with for loop
maybe try this?
var indexes = [];
data.results.forEach(item => {
if(item.title.indexOf(myObjTitle.title)) {
indexes.push(item);
}
});
this.setState({searchString : indexes});
As I understand your question, you are trying to search the element from json Array.I just added the new key to store filterString. Here is my solution
onChange(firstName){
if(firstName != undefined){
this.setState({
nameToFilter : firstName
})
}
}
//Inside the render method
I am using nameToFilter to filter the data.
render() {
let searchString = this.state.searchString
if(this.state.nameToFilter != undefined && this.state.nameToFilter.length>0)
{
searchString = this.state.searchString.filter(item => (
item.title == this.state.nameToFilter
));
}
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={searchString} />}
</div>
</div>
</div>
);
}
It seems you are trying to filter the matching results. In which case it's probably better to use filter instead of map.
onChange(value) {
let results = [];
// Empty string is `falsy`, avoid filtering when no value was given
if (value) {
/*
Pick only one option, remove the other two, or you will
process your data three times instead of once!
*/
// Case-sensitive, exact match
results = data.filter(item => item.title === value);
// Case-insensitive, exact match, value and title are the same when lowercased
results = data.filter(item => (
item.title.toLowerCase() === value.toLowerCase()
));
// Case-insensitive, partial match, value was found *within* title
results = data.filter(item => (
item.title.toLowerCase().indexOf(
value.toLowerCase()
) !== -1
));
}
this.setState({results});
}
Additionally, if you want a single result (the first one that matches), using find instead of filter is better, because it would stop searching after the first match instead of traversing the whole array.
Map
Returns a new array with as many entries as you previously had, but you may return anything you want as the entries, this is why you get so many undefined entries right now. (Because you only return an item when the title matches, hence you implicitly return undefined when not). Map loops through the whole array.
Filter
Returns a new array with as many entries matches your test function. If the function returns true, then the item is included, it it returns false, the item will be omitted. Filter loops through the whole array.
Find
Will only retrieve the first entry that matches your test function. It will stop looping once a match is found.
Bonus
You will most likely need to learn about debouncing, which you can do with lodash.debounce.
Debouncing is a method used to prevent a function from executing many times in a short interval (so you don't unnecessarily re-render)

Categories

Resources