So my page is an Author page which shows different authors and their details in each card which I fetched from API and then mapped.
https://i.stack.imgur.com/eSD7u.png
And in each card after onclick it changes to Remove Favourite. The card which is favourited makes the idfav true in the object array of the author state and false if not favourited. And there is a 2nd page which shows all the favourite authors. Now I am passing it down first as localstorage for the author state but it seems after my 2nd reload if I click on the button irrespective of whether or not the button is add or remove all the other cards/array is removed and only the card on which button I selected shows up.
const [author, setAuthor] = useState([]);
const [AuthorTempState, setAuthorTempState] = useState([]);
// pagination calculation
const [PageNumber, setPageNumber] = useState(0);
const [Postsperpage] = useState(4);
const PagesVisited = PageNumber * Postsperpage;
const pageCount = Math.ceil(author.length / Postsperpage);
const changePage = ({ selected }) => {
setPageNumber(selected);
}
const getAuthors = async () => {
const res = await fetch(`https://api.quotable.io/authors?limit=30`);
const data = await res.json();
for (const element of data.results) {
element.idfav = false;
}
data.results.sort((a, b) => (a._id > b._id) ? 1 : -1)
setAuthor(data.results);
setAuthorTempState(data.results);
}
const saveAuth = () => {
localStorage.setItem('authors', JSON.stringify(author));
}
const getAuth = () => {
const newAuthors = JSON.parse(localStorage.getItem('authors'));
if (newAuthors && newAuthors.length > 0) {
setAuthor(newAuthors);
} else {
getAuthors();
}
}
useEffect(() => {
// console.log((author));
if (author.length === 0) {
getAuth();
}
saveAuth();
}, [author]);
const favBttn = (Auth) => {
const filterData = AuthorTempState.filter(data => data._id !== Auth._id)
Auth.idfav = true;
const updateAuthor = [Auth, ...filterData]
updateAuthor.sort((a, b) => (a._id > b._id) ? 1 : -1)
setAuthor(updateAuthor)
}
const remfavBttn = (Auth) => {
const filterData = AuthorTempState.filter(data => data._id !== Auth._id)
Auth.idfav = false;
const updateAuthor = [Auth, ...filterData]
updateAuthor.sort((a, b) => (a._id > b._id) ? 1 : -1)
setAuthor(updateAuthor);
}
const Author = author.slice(PagesVisited, PagesVisited + Postsperpage)
return (
<div className="AppWhole">
<AuthorSidebar />
<div className="App">
<div className="author">
{Author.map(
(Author) => (
<div className="authors" key={Author._id}>
{
(Author.idfav) ? (<button className='right' onClick={() => {
remfavBttn(Author);
}}>Remove Favt.</button >) : (<button className='right' onClick={() => {
favBttn(Author);
}}>Add Favt.</button >)
}
<p>Name: {Author.name}</p>
<p>Bio: {Author.bio}</p>
<p>Wiki: <a href='{Author.link}'>{Author.link}</a></p>
</div>
))}
<div id='pageauthor'>
<ReactPaginate
pageCount={pageCount}
onPageChange={changePage}
previousLabel={"<<"}
nextLabel={">>"}
containerClassName={'paginationLinks'}
disabledClassName={'paginationDisabled'}
activeClassName={'paginationActive'}
/>
</div>
</div>
</div>
</div>
);
}
export default Authors;
Please help me I have been stuck on this for a week. Thank you.
Okay, once I read your entire code and then read your issue made it pretty clear what's wrong. The issue is here
const favBttn = (Auth) => {
// notice that you are using AuthorTempState to filter data
// but do you remember initialising it when the data is found in local storage?
// AuthorTempState is currently an empty array.
const filterData = AuthorTempState.filter(data => data._id !== Auth._id)
Auth.idfav = true;
const updateAuthor = [Auth, ...filterData]
updateAuthor.sort((a, b) => (a._id > b._id) ? 1 : -1)
setAuthor(updateAuthor)
}
Related
The input loses its focus when I start typing a character. I saw many StackOverflow answers but none of them is working. I have added unique keys also. What is the reason the code is not working? Without the state, it is working fine. But after adding the state, the input loses the focus.
import React, { useState } from "react";
const Footer = ({ formData }) => {
const [colorsArray, setColors] = useState(["Red", "Green", "Blue", "Yellow"]);
const [sizeArray, setSizes] = useState(["S", "M", "L", "XL"]);
const [sizeInput, setsizeInput] = useState("");
const colorElementRemoveHandler = (indexToRemove) => {
const filteredValue = colorsArray.filter((data, index) => {
return indexToRemove !== index;
});
setColors(filteredValue);
};
const sizeElementRemoveHandler = (indexToRemove) => {
const filteredValue = sizeArray.filter((data, index) => {
return indexToRemove !== index;
});
setSizes(filteredValue);
};
const addColorHandler = (e) => {
let input = e.target.value.toLowerCase();
if (input.length > 2) {
let temp = colorsArray;
temp.push(input);
setColors(temp);
}
};
const addSizeHandler = (e) => {
let input = e.target.value.toUpperCase();
if (input.length > 0) {
let temp = sizeArray;
temp.push(input);
setSizes(temp);
console.log(sizeArray);
}
};
const Test = () => {
return (
<input
type="text"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
);
};
const VariantUI = () => {
return (
<div>
<label>Size</label>
<input
id="optionName"
type="text"
placeholder="e.g S, M, L, XL"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
</div>
<ul>
{sizeArray.map((data, index) => {
return (
<li key={index}>
{data}
<i onClick={() => {sizeElementRemoveHandler(index);}}></i>
</li>
);
})}
</ul
);
};
return (
<VariantUI formData={formData} />
);
};
export default Footer;
Thanks in advance.
const Footer = ({ formData }) => {
// ..
const VariantUI = () => {
// ...
return (<VariantUI formData={formData} />)
}
You are creating a brand new type of component (VariantUI), in the middle of rendering Footer. This will happen on ever render. Each VariantUi function might have the same text as the previous one, but it's a different function, and thus to react it's a different type of component. Since it's a different type of component, the old one unmounts, and the new one mounts. A newly-mounted <input> does not have focus.
Component types must be defined only once, not on ever render. So VariantUI needs to be moved outside of footer. Since you're currently relying on closure variables, you will need to changes those to props:
const VariantUI = ({
sizeArray, setSizes, sizeInput, setSizeInput, // I might have missed a couple props
}) => {
// ...
}
const Footer = ({ formData }) => {
// ...
return (
<VariantUI
sizeArray={sizeArray}
setSizes={setSizes}
sizeInput={sizeInput}
setSizeInput={setSizeInput}
/>
);
}
I'm building this website with MERN stack and having this rendering bug:
On start, I have a foodList table rendering out all of the food in the database.
I already have a useEffect() with the foodList inside the dependency array - so anytime the users make changes to the foodList table (Add/Edit/Delete), it will instantly render out that added dish without refreshing the page.
When users search for something in this Search & Filter bar, it will hide the foodList table and return a table of searchedFood that is filtered from the foodList array.
But when the users use this Search & Filter functionality and then try to Edit/Delete from that searchedFood table. It won't render the changes instantly - they have to refresh the page to see the changes they made.
This might relate to the useEffect() but I don't know how to apply it for the searchedFood table without disrupting the foodList table.
App.js
export default function App() {
const [foodName, setFoodName] = useState('')
const [isVegetarian, setIsVegetarian] = useState('no')
const [priceRange, setPriceRange] = useState('$')
const [foodUrl, setFoodUrl] = useState('')
const [foodList, setFoodList] = useState([])
const [searchedFood, setSearchedFood] = useState([])
const [noResult, setNoResult] = useState(false)
// Display food list:
useEffect(() => {
let unmounted = false
Axios.get("https://project.herokuapp.com/read")
.then((response) => {
if (!unmounted) {
setFoodList(response.data)
}
})
.catch(error => {
console.log(`The error is: ${error}`)
return
})
return () => {
unmounted = true
}
}, [foodList])
// Add Food to list:
const addToList = async (event) => {//Axios.post logic in here}
// Paginate states:
const [currentPage, setCurrentPage] = useState(1)
const [foodPerPage] = useState(5)
// Get current food:
const indexOfLastFood = currentPage * foodPerPage
const indexOfFirstFood = indexOfLastFood - foodPerPage
const currentFood = foodList.slice(indexOfFirstFood, indexOfLastFood)
const currentSearchedFood = searchedFood.slice(indexOfFirstFood, indexOfLastFood)
const paginate = (pageNumber) => {
setCurrentPage(pageNumber)
}
return (
<section>
<FilterSearch
foodList={foodList}
searchedFood={searchedFood}
setSearchedFood={setSearchedFood}
noResult={noResult}
setNoResult={setNoResult}
paginate={paginate}
/>
{noResult ? <ResultNotFound/>
:
<FoodListTable
foodName={foodName}
priceRange={priceRange}
isVegetarian={isVegetarian}
foodUrl={foodUrl}
foodList={foodList}
currentFood={currentFood}
searchedFood={searchedFood}
currentSearchedFood={currentSearchedFood}
totalFood={foodList.length}
totalSearchedFood={searchedFood.length}
currentPage={currentPage}
paginate={paginate}
noResult={noResult}
foodPerPage={foodPerPage}
/>
}
</section>
)
}
FoodListTable.js
export default function FoodListTable(props) {
return (
<div>
<table>
<thead>
<tr>
<th>
Food name
</th>
<th>Price</th>
<th>
Action
</th>
</tr>
</thead>
<body>
// Return a table with data from searchFood on search:
{props.searchedFood.length > 0 ? props.currentSearchedFood.map((val) => {
return (
<FoodListRow
val={val}
key={val._id}
foodName={val.foodName}
isVegetarian={val.isVegetarian}
priceRange={val.priceRange}
foodUrl={val.foodUrl}
/>
)
}) : props.currentFood.map((val) => { // If not on search, return a table with data from foodList:
return (
<FoodListRow
val={val}
key={val._id}
foodName={val.foodName}
isVegetarian={val.isVegetarian}
priceRange={val.priceRange}
foodUrl={val.foodUrl}
/>
)
})
}
</tbody>
</table>
// Display different Pagination on searched table and food list table:
{props.searchedFood.length > 0 ?
<Pagination foodPerPage={props.foodPerPage} totalFood={props.totalSearchedFood} paginate={props.paginate} currentPage={props.currentPage} />
:<Pagination foodPerPage={props.foodPerPage} totalFood={props.totalFood} paginate={props.paginate} currentPage={props.currentPage} />
}
</div>
)
}
FoodListRow.js
export default function FoodListRow(props) {
// Edit food name:
const [editBtn, setEditBtn] = useState(false)
const handleEdit = () => {
setEditBtn(!editBtn)
}
// Update Food Name:
const [newFoodName, setNewFoodName] = useState('')
const updateFoodName = (id) => {
if (newFoodName) {
Axios.put("https://project.herokuapp.com/update", {
id: id,
newFoodName: newFoodName,
})
.catch(error => console.log(`The error is: ${error}`))
}
}
// Delete food:
const deleteFood = (id) => {
const confirm = window.confirm(`This action cannot be undone.\nAre you sure you want to delete this dish?`);
if(confirm === true){
Axios.delete(`https://project.herokuapp.com/delete/${id}`)
}
}
return (
<tr key={props.val._id}>
<td>
{props.val.foodName}
{editBtn &&
<div>
<input
type="text"
name="edit"
placeholder="New food name.."
autoComplete="off"
onChange={(event) => {setNewFoodName(event.target.value)}}
/>
<button
onClick={() => updateFoodName(props.val._id)}
>
✓
</button>
</div>
}
</td>
<td>{props.val.priceRange}</td>
<td>
<a
href={props.val.foodUrl}
target="_blank"
rel="noopener noreferrer"
>
🔗
</a>
<button
onClick={handleEdit}
>
✏️
</button>
<button
onClick={() => deleteFood(props.val._id)}
>
❌
</button>
</td>
</tr>
);
}
As Mohd Yashim Wong mentioned, we need to re-render every time there's change to the backend.
I ditched the foodList inside the useEffect()'s dependency array and try another method because this is not the correct way to re-render the axios calls. It just keeps sending read requests indefinitely if I use this way. That might be costly.
This is what I have switched to:
I set the dependency array empty
Pull the data from the backend and return it to the frontend after the axios calls
addToList function:
const addToList = async (event) => {
event.preventDefault()
try {
await Axios.post(
"https://project.herokuapp.com/insert",
{
foodName: foodName,
isVegetarian: isVegetarian,
priceRange: priceRange,
foodUrl: foodUrl,
}
)
.then((response) => {
// Return the data to the UI:
setFoodList([...foodList, { _id: response.data._id, foodName: foodName, isVegetarian: isVegetarian, priceRange: priceRange, foodUrl: foodUrl }])
setFoodName('')
setIsVegetarian('no')
setPriceRange('$')
setFoodUrl('')
})
} catch(err) {
console.error(`There was an error while trying to insert - ${err}`)
}
}
updateFoodName function:
const updateFoodName = (id) => {
if (newFoodName) {
Axios.put("https://project.herokuapp.com/update", {
id: id,
newFoodName: newFoodName,
})
.then(() => {
// Update on searchedFood:
props.searchedFood.length > 0 ?
props.setSearchedFood(props.searchedFood.map((val) => {
return (
val._id === id ?
{
_id: id,
foodName: newFoodName,
isVegetarian: props.isVegetarian, priceRange: props.priceRange,
foodUrl: props.foodUrl,
} : val
)
})) //Update on foodList
: props.setFoodList(props.foodList.map((val) => {
return (
val._id === id ?
{
_id: id,
foodName: newFoodName,
isVegetarian: props.isVegetarian, priceRange: props.priceRange,
foodUrl: props.foodUrl,
} : val
)
}))
})
.catch(error => console.log(`Update name failed: ${error}`))
}
}
deleteFood function:
const deleteFood = (id) => {
const confirm = window.confirm(`This action cannot be undone.\nAre you sure you want to delete this dish?`);
if(confirm === true){
Axios.delete(`https://project.herokuapp.com/delete/${id}`)
.then(() => {
props.searchedFood.length > 0
? props.setSearchedFood(props.searchedFood.filter((val) => {
return val._id !== id
}))
: props.setFoodList(props.foodList.filter((val) => {
return val._id !== id
}))
})
}
}
You are never updating the text of the food name. Inside FoodListRow, you should create a state for the name of the food. Set this equal to props.val.foodName and then update it at the end of updateFoodName() after the axios request.
Currently have a phonebook App created with React, that will take a name and a number from a form and display it on screen and also store the data on a server.
I am looking to improve the functionality so that if a number is added to an already existing user, the app will ask if you would like to replace the old number with the new number.
Here is my function for adding an entry to the phone book
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
number: newNumber,
}
if (persons.some(person =>person.name ===
`${newName}`) )
{
alert(`${newName}` + " is already added to phonebook, replace the old number with a new one?")
}
else
{
personsService
.create(nameObject)
.then(returnedName => {
setPersons(persons.concat(returnedName))
setNewName('')
setNewNumber('')
console.log(persons.id)
})
}
I created a new function to update the entry
const changeNumberOf = id => {
const url = `http://localhost:3001/persons/${id}`
const person = persons.find(n => n.id === id)
const changedPerson = { ...person} // here I need to update to the new number
axios.put(url, changedPerson).then(response => {
setPersons(persons.map(person => person.id !== id ? person : response.data))
})
}
My question is how will I get the Id of the entry I need to update from my addName function so I can then update it with the new phone number?
Picture added below of what app will look like when functioning correctly.
Any help appreciated
Full code attached below
const App = () => {
const [persons, setPersons] = useState([
])
const [ newName, setNewName ] = useState('')
const [ newNumber, setNewNumber] = useState('')
const [ searchTerm, setSearchTerm] = useState('')
useEffect(() => {
personsService
.getAll()
.then(currentNames => {
setPersons(currentNames)
})
}, [])
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
number: newNumber,
}
if (persons.some(person =>person.name ===
`${newName}`) )
{
alert(`${newName}` + " is already added to phonebook, replace the old number with a new one?")
}
else
{
personsService
.create(nameObject)
.then(returnedName => {
setPersons(persons.concat(returnedName))
setNewName('')
setNewNumber('')
console.log(persons.id)
})
}
}
const handleNewName = (event) => {
// console.log(event.target.value)
setNewName(event.target.value)
}
const handleNewNumber = (event) => {
setNewNumber(event.target.value)
}
const deletePersons = id => {
const requestOptions = {
method: 'DELETE'
};
fetch("http://localhost:3001/persons/" + id , requestOptions)
personsService
.getAll()
.then(initialNames => {
setPersons(initialNames)
}
)}
const changeNumberOf = id => {
const url = `http://localhost:3001/persons/${id}`
const person = persons.find(n => n.id === id)
const changedPerson = { ...person, important: !person.important }
axios.put(url, changedPerson).then(response => {
setPersons(persons.map(person => person.id !== id ? person : response.data))
})
}
return (
<div classname="App">
<h2>Phonebook</h2>
<input
type="text"
placeholder="Search..."
onChange={event => {setSearchTerm(event.target.value)}} />
<h2>Add a new</h2>
<form onSubmit={addName}>
<div>
name: <input
value={newName}
onChange={handleNewName}
/>
</div>
<div>
number: <input
value={newNumber}
onChange={handleNewNumber}
/>
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
{persons.filter((val) => {
if (searchTerm == "") {
return val
}
else if (val.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return val
}
}) .map(person =>
<div classname="user">
<li key={person.name}>{person.name}{person.number}
</li>
</div>
)
}
</div>
)
}
export default App
You can replace the persons.some() with a for loop:
let personMatch = null;
for(const person of persons){
if(person.name === newName){
personMatch = person;
// stop the loop, we have found someone
break;
}
}
if(personMatch !== null){
// changeNumberOf(personMatch.id) I guess
} else {
// no matching person found
}
I am trying to manipulate the images shown in my React App by voice. I implemented the SR, which works fine:
<button onClick={SpeechRecognition.startListening}>Start</button>
I have an array of loadImages in my state, which is empty first:
const [loadImages, setLoadImages] = React.useState([]);
Whenever the word spoken is "kitten", the array of loadImages gets updated in this way:
if(transcript == "kitten")
{
const newImages = loadImages.concat({image: 'https://www.onlinekittencare.com/wp-content/uploads/2020/07/vChK6pTy3vN3KbYZ7UU7k3-1200-80.jpg'})
setLoadImages(newImages);
}
The transcript you see is a variable, which I initialized here:
const {transcript} = useSpeechRecognition();
In the render I use it to show what the SR understood, so if I say "hello" it shows "hello":
<p id="transcript">Transcript: {transcript}</p>
And this is where the images in loadImages show up:
{images.map((image) => {
return <URLImage image={image}/>;
})}
The problem is that whenever I say "kitten", which as stated above is used as a command to add the picture to the array loadImages, my React App gets blank. In the inspect I can also see that it says react-dom.development.js:14997 Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
How do I fix that?
EDIT (I added the whole code):
function App() {
const [rectangles, setRectangles] = useState([]);
const [circles, setCircles] = useState([]);
const [selectedId, selectShape] = useState(null);
const [shapes, setShapes] = useState([]);
const [, updateState] = React.useState();
const stageEl = React.createRef();
const layerEl = React.createRef();
const fileUploadEl = React.createRef();
const [brushSize, setBrushSize] = React.useState('5');
const [isDrawing, setIsDrawing] = React.useState(false);
const dragUrl = React.useRef();
//const stageRef = React.useRef();
const [images, setImages] = React.useState([]);
const [loadImages, setLoadImages] = React.useState([]);
const getRandomInt = max => {
return Math.floor(Math.random() * Math.floor(max));
};
const {transcript} = useSpeechRecognition();
const URLImage = ({image}) => {
const [img] = useImage(image.src);
return (
<Image
image = {img}
x = {image.x}
y = {image.y}
offsetX = {50}
offsetY = {50}
width={200}
height={200}
draggable
/>
);
};
const drawLine = () => {
setIsDrawing(true);
if(isDrawing){
addLine(stageEl.current.getStage(), layerEl.current, brushSize);
};
};
const eraseLine = () => {
addLine(stageEl.current.getStage(), layerEl.current, brushSize, "erase");
};
const addRectangle = () => {
setIsDrawing(false);
const rect = {
x: getRandomInt(100),
y: getRandomInt(100),
width: 100,
height: 100,
fill: "red",
id: `rect${rectangles.length + 1}`,
};
const rects = rectangles.concat([rect]);
setRectangles(rects);
const shs = shapes.concat([`rect${rectangles.length + 1}`]);
setShapes(shs);
};
const forceUpdate = React.useCallback(() => updateState({}), []);
const undo = () => {
const lastId = shapes[shapes.length - 1];
let index = circles.findIndex(c => c.id == lastId);
if (index != -1) {
circles.splice(index, 1);
setCircles(circles);
}
index = rectangles.findIndex(r => r.id == lastId);
if (index != -1) {
rectangles.splice(index, 1);
setRectangles(rectangles);
}
index = images.findIndex(r => r.id == lastId);
if (index != -1) {
images.splice(index, 1);
setImages(images);
}
shapes.pop();
setShapes(shapes);
forceUpdate();
};
document.addEventListener("keydown", ev => {
if (ev.code == "Delete") {
let index = circles.findIndex(c => c.id == selectedId);
if (index != -1) {
circles.splice(index, 1);
setCircles(circles);
}
index = rectangles.findIndex(r => r.id == selectedId);
if (index != -1) {
rectangles.splice(index, 1);
setRectangles(rectangles);
}
index = images.findIndex(r => r.id == selectedId);
if (index != -1) {
images.splice(index, 1);
setImages(images);
}
forceUpdate();
}
});
if(transcript == "kitten")
{
const newImages = loadImages.concat({image: 'https://www.onlinekittencare.com/wp-content/uploads/2020/07/vChK6pTy3vN3KbYZ7UU7k3-1200-80.jpg'})
setLoadImages(newImages);
}
return (
<div className="home-page">
{loadImages.map(image => (
<img id="img" className="img"
src={image.image}
width="200"
height="200"
onDragStart={(e) => {
dragUrl.current = e.target.src;}}
/>
))}
<div
onDrop={(e) => {
e.preventDefault();
// register event position
stageEl.current.setPointersPositions(e);
// add image
setImages(
images.concat([
{
...stageEl.current.getPointerPosition(),
src: dragUrl.current,
},
])
);
}}
onDragOver={(e) =>
e.preventDefault()
}
>
<h1>Whiteboard</h1>
<button onClick={addRectangle}>
Rectangle
</button>
<button>
Circle
</button>
<button onClick={drawLine}>
Line
</button>
<button onClick={eraseLine}>
Erase
</button>
<select
value={brushSize}
onChange={(e) => {
setBrushSize(e.target.value);
drawLine();
}}
>
<option value="5">5</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
<button variant="secondary">
Text
</button>
<button variant="secondary">
Image
</button>
<button variant="secondary" onClick={undo}>
Undo
</button>
<p id="transcript">Transcript: {transcript}</p>
<button onClick={SpeechRecognition.startListening}>Start</button>
<Stage
width={window.innerWidth * 0.9}
height={window.innerHeight - 150}
ref={stageEl}
dragabble
onMouseDown={e => {
// deselect when clicked on empty area
const clickedOnEmpty = e.target === e.target.getStage();
if (clickedOnEmpty) {
selectShape(null);
}
}}
>
<Layer ref={layerEl}>
{rectangles.map((rect, i) => {
return (
<Rectangle
key={i}
shapeProps={rect}
isSelected={rect.id === selectedId}
//onSelect={() => {
// selectShape(rect.id);
//}}
onChange={newAttrs => {
const rects = rectangles.slice();
rects[i] = newAttrs;
setRectangles(rects);
}}
/>
);
})}
{images.map((image) => {
return <URLImage image={image}/>;
})}
</Layer>
</Stage>
</div>
</div>
);
}
export default App;
Based on the code you've shared, it has to do with how you're updating the state if the transcript is equal to kitten.
Essentially, the logic you've written says, on render, if the transcript is kitten, update the state. BUT, when you update the state, that will re-render, and hit that logic again... and again... and again. The solution here is to wrap that in a useEffect – React Docs explain it best but in simple terms, you want to "do something" as a side effect of "something else".
In this case, if the transcript updates, you want to check the state of transcript, and if it meets a condition, you want to update your state:
React.useEffect(() => {
if (transcript === "kitten") {
const newImages = loadImages.concat({image: 'https://www.onlinekittencare.com/wp-content/uploads/2020/07/vChK6pTy3vN3KbYZ7UU7k3-1200-80.jpg'})
setLoadImages(newImages);
}
}, [transcript]);
The final piece to the useEffect is a dependency array ([transcript])–this dictates to React which item you want to watch for changes in–if transcript changes, it will run your effect and only when it changes, instead of every time it renders.
I have tried to create an autocomplete suggestion box from an Thailand's province database URL.
This is my source code. I export this to App.js in src directory
import React, { useEffect, useState, useRef } from "react";
const Test = () => {
const [display, setDisplay] = useState(false);
const [singleProvince, setSingleProvince] = useState([]);
const [singleProvinceData, setSingleProvinceData] = useState([]);
const [search, setSearch] = useState("");
const wrapperRef = useRef(null);
const province_dataBase_url = 'https://raw.githubusercontent.com/earthchie/jquery.Thailand.js/master/jquery.Thailand.js/database/raw_database/raw_database.json'
useEffect(() => {
const promises = new Array(20).fill(fetch(province_dataBase_url)
.then((res) => {
return res.json().then((data) => {
const createSingleProvince = data.filter( (each) => {
if (false == (singleProvince.includes(each.province))) {
setSingleProvince(singleProvince.push(each.province))
setSingleProvinceData(singleProvinceData.push(each))
}
})
return data;
}).catch((err) => {
console.log(err);
})
}))
}, [])
useEffect(() => {
window.addEventListener("mousedown", handleClickOutside);
return () => {
window.removeEventListener("mousedown", handleClickOutside);
};
});
const handleClickOutside = event => {
const { current: wrap } = wrapperRef;
if (wrap && !wrap.contains(event.target)) {
setDisplay(false);
}
};
const updateProvince = inputProvince => {
setSearch(inputProvince);
setDisplay(false);
};
return (
<div ref={wrapperRef} className="flex-container flex-column pos-rel">
<input
id="auto"
onClick={() => setDisplay(!display)}
placeholder="Type to search"
value={search}
onChange={event => setSearch(event.target.value)}
/>
{display && (
<div className="autoContainer">
{ singleProvinceData
.filter( ({province}) => province.indexOf(search.toLowerCase()) > -1)
.map( (each,i) => {
return (
<div
onClick={() => updateProvince(each.province)}
className="singleProvinceData"
key={i}
tabIndex="0"
>
<span>{each.province}</span>
</div>
)
})}
</div>
)}
</div>
);
}
export default Test
When click on an input box, the console says "TypeError: singleProvinceData.filter is not a function"
enter image description here
I cannot find out what's wrong with my code
The issue is with the "singleProvinceData" state is not set correctly.
you cannot push data directly into the state.
useEffect(() => {
const promises = new Array(20).fill(fetch(province_dataBase_url)
.then((res) => {
return res.json().then((data) => {
const shallowSingleProvinceList = [];
const shallowSingleProvinceDataList = [];
const createSingleProvince = data.filter( (each) => {
if (false == (singleProvince.includes(each.province))) {
shallowSingleProvinceList.push(each.province)
shallowSingleProvinceDataList.push(each)
}
})
setSingleProvince(shallowSingleProvinceList)
setSingleProvinceData(shallowSingleProvinceDataList)
return data;
}).catch((err) => {
console.log(err);
})
}))
}, [])
You can show the data conditionally
{display && (
<div className="autoContainer">
{ singleProvinceData && singleProvinceData
.filter( ({province}) => province.indexOf(search.toLowerCase()) > -1)
.map( (each,i) => {
return (
<div
onClick={() => updateProvince(each.province)}
className="singleProvinceData"
key={i}
tabIndex="0"
>
<span>{each.province}</span>
</div>
)
})}
</div>
)}