React Question: Undefined Values passing data into props - javascript

I have been struggling to find a solution to this problem. I'm getting TypeError: Cannot read property 'Department_ID' of undefined errors from props being passed in from parent state. The main problem I'm seeing is that the Departments data is not being passed to the child element. Please look at the code and tell me what I'm missing! Thank you in advance for whatever help you can give me!
Parent Component: Positions
import {useEffect, useState} from 'react'
import API_access from '../../API_access'
import Position from './Position';
function Positions() {
const [positions, setPositions] = useState([]);
const [departments, setDepartments] = useState([]);
const [_position_ID, setPositionID] = useState('');
const [_title, setTitle] = useState('');
const [_department_ID, setDepartmentID] = useState();
const [_department_name, setDepartmentName] = useState();
useEffect(() => {
const get_data = async() => {
const jobs = await API_access('/API/Positions', 'GET');
const depts = await API_access('/API/Departments', 'GET');
setPositions(jobs.data);
setDepartments(depts.data);
}
get_data();
},[])
const Create_Position = async () => {
let new_position = {
Position_ID: _position_ID,
Position_Title: _title,
Department_ID: _department_ID
}
const success = await API_access('/API/Positions', 'POST', new_position);
if (success) {
let tmpArray = positions;
tmpArray.push(new_position);
setPositions(tmpArray);
}
}
const Update_Position = async (position) => {
const success = await API_access('/API/Positions', 'PATCH', position);
if (success) {
let tmpArray = positions.filter(pos => pos.Position_ID !== position.Position_ID);
tmpArray.push(position);
setPositions(tmpArray);
}
}
const Delete_Position = async (position) => {
const success = await API_access('/API/Positions', position);
if(success) {
let tmpArray = positions.filter(pos => pos.Position_ID !== position.Position_ID);
setPositions(tmpArray);
}
}
const Set_Department_Data = (event) => {
setDepartmentName(event.target.options[event.target.selectedIndex].text);
setDepartmentID(event.target.value);
}
return (
<div>
<form>
<input type="text" name="txtPositionID" value={_position_ID} onChange={event => setPositionID(event.target.value)}/>
<label htmlFor="txtPositionID">Position ID</label>
<input type="text" name="txtTitle" value={_title} onChange={event => setTitle(event.target.value)}/>
<label htmlFor="txtTitle">Job Title</label>
<select value={_department_ID} text={_department_name} onChange={event => Set_Department_Data(event)}>
<option value="" disabled>Select Department</option>
{departments.map((department, index) => (
<option key={index} value={department.Department_ID}>{department.Department_Name}</option>
))}
</select>
<button onClick={() => Create_Position}>Save</button>
</form>
<table>
<thead>
<tr>
<td>Edit</td>
<td>Position ID</td>
<td>Job Title</td>
<td>Department</td>
<td>Delete</td>
</tr>
</thead>
<tbody>
{positions.length > 0 && positions.map((position, index) => (
<Position key={index} Position={position} Departments={departments} Update_Position={Update_Position} Delete_Position={Delete_Position}/>
))}
</tbody>
</table>
</div>
)
}
export default Positions
Child Element: Position
import {useState, useEffect} from 'react'
function Position({ Position, Departments, Update_Position, Delete_Position }) {
const [_department_ID, setDepartmentID] = useState(Position.Department_ID);
const [_department_name, setDepartmentName] = useState('');
const [_job_title, setJobTitle] = useState(Position.Position_Title);
const [_edit, toggleEdit] = useState(false);
useEffect(() => {
const set_data = () => {
console.log(Departments)
console.log(_department_ID)
let dept = Departments.find(d => String(d.Department_ID) === String(_department_ID));
console.log(dept)
setDepartmentID(dept.Department_ID);
setDepartmentName(dept.Department_Name)
}
set_data();
}, [])
const Set_Department_Data = (event) => {
setDepartmentName(event.target.options[event.target.selectedIndex].text);
setDepartmentID(event.target.value);
}
const Update_This_Position = () => {
if (_edit) {
let pos = {
Position_ID: Position.Position_ID,
Position_Title: _job_title,
Department_ID: _department_ID
}
Update_Position(pos);
}
toggleEdit(!_edit);
}
if (_edit) {
return (
<tr>
<td><button onClick={() => Update_This_Position()}>Save</button></td>
<td>{Position.Position_ID}</td>
<td><input type="text" value={_job_title} onChange={event => setJobTitle(event.target.value)}/> </td>
<td><select value={_department_ID} text={_department_name} onChange={event => Set_Department_Data(event)}>
{Departments.map((department, index) => (
<option key={index} value={department.Department_ID}>{department.Department_Name}</option>
))}
</select></td>
<td><button onClick={() => Delete_Position(Position)}>X</button></td>
</tr>
)
} else {
return (
<tr>
<td><button onClick={() => Update_This_Position()}>></button></td>
<td>{Position.Position_ID}</td>
<td>{Position.Position_Title}</td>
<td>{_department_name}</td>
<td><button onClick={() => Delete_Position(Position)}>X</button></td>
</tr>
)
}
}
export default Position
Error:
TypeError: Cannot read property 'Department_ID' of undefined
set_data
src/components/Personnel/Position.js:15
12 | console.log(_department_ID)
13 | let dept = Departments.find(d => String(d.Department_ID) === String(_department_ID));
14 | console.log(dept)
> 15 | setDepartmentID(dept.Department_ID);
| ^ 16 | setDepartmentName(dept.Department_Name)
17 | }
18 | set_data();
View compiled
(anonymous function)
src/components/Personnel/Position.js:18
15 | setDepartmentID(dept.Department_ID);
16 | setDepartmentName(dept.Department_Name)
17 | }
> 18 | set_data();
| ^ 19 | }, [])
20 |
21 | const Set_Department_Data = (event) => {
View compiled
▶ 16 stack frames were collapsed.
get_data
src/components/Personnel/Positions.js:18
15 | const jobs = await API_access('/API/Positions', 'GET');
16 | const depts = await API_access('/API/Departments', 'GET');
17 | setPositions(jobs.data);
> 18 | setDepartments(depts.data);
| ^ 19 | }
20 | get_data();
21 | },[])
View compiled
This screen is visible only in development. It will not appear if the app crashes in production.
Open your browser’s developer console to further inspect this error. Click the 'X' or hit ESC to dismiss this message.

Issue
Array.prototype.find can return undefined if no element passes the predicate condition.
The find() method returns the value of the first element in the
provided array that satisfies the provided testing function. If no
values satisfy the testing function, undefined is returned.
During the initial render cycle departments is an empty array, so dept is all but guaranteed to be undefined on the initial render.
const set_data = () => {
console.log(Departments)
console.log(_department_ID)
let dept = Departments.find(d => String(d.Department_ID) === String(_department_ID));
console.log(dept)
setDepartmentID(dept.Department_ID);
setDepartmentName(dept.Department_Name)
}
Solution
You should check for valid results from the find before accessing the result.
const set_data = () => {
const deptId = String(_department_ID);
const dept = Departments.find(d => String(d.Department_ID) === deptId);
if (dept) {
setDepartmentID(dept.Department_ID);
setDepartmentName(dept.Department_Name);
}
}
BUT, this code is running in an useEffect hook with an empty dependency, so the hook runs only once when the component mounts. You've a couple options:
Add a dependency to the useEffect hook to set the component state when the Departments prop updates.
useEffect(() => {
const set_data = () => {
const deptId = String(_department_ID);
const dept = Departments.find(d => String(d.Department_ID) === deptId);
if (dept) {
setDepartmentID(dept.Department_ID);
setDepartmentName(dept.Department_Name);
}
}
set_data();
}, [Departments]); // <-- dependency
Conditionally render Position only when all of the props are populated. Ensure both positions and departments arrays have a truthy (non-zero) length.
<tbody>
{positions.length && departments.length && positions.map((position, index) => (
<Position
key={index}
Position={position}
Departments={departments}
Update_Position={Update_Position}
Delete_Position={Delete_Position}
/>
))}
</tbody>

Related

Data-testid = 'suggestion' is not being detected while running testcases

I have prepared a wikipedia search react application which uses api to get the data .
Below here is the UI for the application .
Here is the code of the "Search" component
import React, { useState } from 'react';
import axios from 'axios';
function Search() {
const [searchTerm, setSearchTerm] = useState('Programming');
const [suggestions, setSuggestions] = useState([]);
let timeoutId = null;
const handleSearch = (e) => {
const searchValue = e.target.value;
setSearchTerm(searchValue);
clearTimeout(timeoutId);
if (searchValue.length === 0) {
setTimeout(() => setSuggestions([]), 200);
} else {
timeoutId = setTimeout(() => {
axios.get(`https://en.wikipedia.org/w/api.php?action=opensearch&origin=*&search=${searchValue}`)
.then(res => {
const suggestionsData = res.data[1];
const suggestionsLinks = res.data[3];
const suggestionsList = suggestionsData.map((text, index) => {
return {
text,
link: suggestionsLinks[index]
};
});
setSuggestions(suggestionsList);
})
.catch(err => console.log(err));
}, 500);
}
};
return (
<div className='search'>
<h1 align='center'> Wiki Search</h1>
<input
data-testid="searchterm"
value={searchTerm}
onChange={handleSearch}
/>
{suggestions.map((suggestion, index) => (
<div data-testid="suggestion" style={{backgroundColor:index%2?'#e2c3e7':'#e9d6ec'}}>
<a
key={index}
href={suggestion.link}
>
{suggestion.text} <br></br>
</a>
</div>
))}
</div>
);
}
export default Search;
import { fireEvent, render, screen, waitFor, cleanup, queryByTestId, getByTestId, wait } from "#testing-library/react";
import Search from "./components/Search/Search";
import axios from 'axios';
import fetchData from "./components/Search/utillity";
jest.mock('axios');
console.log(axios);
describe('Search Component', () => {
afterEach(cleanup);
test('testcase1', async () => {
const dataList = {
data : [
'R',
['R', 'Ring', 'Revengers', 'Robusted'],
['', '', '', '', ''],
['www.google.com', 'www.yahoo.com', 'www.duckduckgo.com', 'www.brave.com']
]
}
const { getAllByTestId, queryAllByTestId } = render(<Search />);
axios.get.mockImplementationOnce(() => Promise.resolve(dataList));
let flag = true;
const searchname = ['R', 'Ring', 'Revengers', 'Robusted'];
const searchLinks = ['www.google.com', 'www.yahoo.com', 'www.duckduckgo.com', 'www.brave.com']
console.log("Log ===", searchLinks);
const suggestionList = await screen.findAllByTestId('suggestion');
await suggestionList.forEach((suggestion, index) => {
// console.log(`test = ${suggestion.textContent} exp = ${searchname[index]} at ind = ${index}`);
// console.log(`test = ${suggestion.getAttribute('href')} exp = ${searchLinks[index]} at ind = ${index}`);
expect(suggestion.textContent).toBe(searchname[index]);
expect(suggestion.getAttribute('href')).toBe(searchLinks[index])
})
})
test('testcase2', async () => {
const dataList = {
data : [
'R',
['R', 'Ring', 'Revengers', 'Robusted', 'Revamped'],
['', '', '', '', '', ''],
['www.google.com', 'www.yahoo.com', 'www.duckduckgo.com', 'www.brave.com', 'www.lickhack.go']
]
}
const { getAllByTestId, queryAllByTestId } = render(<Search />);
axios.get.mockImplementationOnce(() => Promise.resolve(dataList));
let searchname = [...dataList.data[1]];
let searchLinks = [...dataList.data[3]];
let suggestionList = await screen.findAllByTestId('suggestion');
await suggestionList.forEach((suggestion, index) => {
// console.log(`test = ${suggestion.textContent} exp = ${searchname[index]} at ind = ${index}`);
// console.log(`test = ${suggestion.getAttribute('href')} exp = ${searchLinks[index]} at ind = ${index}`);
expect(suggestion.textContent).toBe(searchname[index]);
expect(suggestion.getAttribute('href')).toBe(searchLinks[index])
})
});
test('testcase3', async () => {
const dataList = {
data : [
'R',
['R', 'Ring', 'Revengers', 'Robusted', 'Revamped'],
['', '', '', '', '', ''],
['www.google.com', 'www.yahoo.com', 'www.duckduckgo.com', 'www.brave.com', 'www.lickhack.go']
]
}
render(<Search />);
axios.get.mockImplementationOnce(() => Promise.resolve(dataList));
let searchname = [...dataList.data[1]];
let searchLinks = [...dataList.data[3]];
let suggestionList = await screen.findAllByTestId('suggestion');
let input = screen.getByTestId('searchterm');
await fireEvent.change(input, {target : {value : ''}});
await new Promise((r) => setTimeout(r, 300));
await waitFor(() => {
expect(screen.queryAllByTestId('suggestion')).toEqual([]);
})
})
})
While it was working fine when the application is running , not when it is tested against these testcases .
Here is the error message that gets displayed after testcase fails
FAIL src/App.test.js
Search Component
× testcase1 (1061 ms)
× testcase2 (1015 ms)
× testcase3 (1021 ms)
● Search Component › testcase1
Unable to find an element by: [data-testid="suggestion"]
Ignored nodes: comments, script, style
<body>
<div>
<div
class="search"
>
<h1
align="center"
>
Wiki Search
</h1>
<input
data-testid="searchterm"
value="Programming"
/>
</div>
</div>
</body>
33 | /* eslint-disable */;(oo_oo(),console.log("Log ===", searchLinks, `3708101a_1`));
34 |
> 35 | const suggestionList = await screen.findAllByTestId('suggestion');
| ^
36 |
37 | await suggestionList.forEach((suggestion, index) => {
38 |
at waitForWrapper (node_modules/#testing-library/dom/dist/wait-for.js:166:27)
at findAllByTestId (node_modules/#testing-library/dom/dist/query-helpers.js:86:33)
at Object.<anonymous> (src/App.test.js:35:45)
● Search Component › testcase2
Unable to find an element by: [data-testid="suggestion"]
Ignored nodes: comments, script, style
<body>
<div>
<div
class="search"
>
<h1
align="center"
>
Wiki Search
</h1>
<input
data-testid="searchterm"
value="Programming"
/>
</div>
</div>
</body>
65 | let searchLinks = [...dataList.data[3]];
66 |
> 67 | let suggestionList = await screen.findAllByTestId('suggestion');
| ^
68 |
69 | await suggestionList.forEach((suggestion, index) => {
70 |
at waitForWrapper (node_modules/#testing-library/dom/dist/wait-for.js:166:27)
at findAllByTestId (node_modules/#testing-library/dom/dist/query-helpers.js:86:33)
at Object.<anonymous> (src/App.test.js:67:43)
● Search Component › testcase3
Unable to find an element by: [data-testid="suggestion"]
Ignored nodes: comments, script, style
<body>
<div>
<div
class="search"
>
<h1
align="center"
>
Wiki Search
</h1>
<input
data-testid="searchterm"
value="Programming"
/>
</div>
</div>
</body>
97 | let searchLinks = [...dataList.data[3]];
98 |
> 99 | let suggestionList = await screen.findAllByTestId('suggestion');
| ^
100 |
101 | let input = screen.getByTestId('searchterm');
102 |
at waitForWrapper (node_modules/#testing-library/dom/dist/wait-for.js:166:27)
at findAllByTestId (node_modules/#testing-library/dom/dist/query-helpers.js:86:33)
at Object.<anonymous> (src/App.test.js:99:43)
Can someone tell what should i do so the application passed through testcases . Please help me out.

React Form loses input on reload

im having trouble with a project of mine with the useState hook. This is for a declaration of an event.
This component can be opened from a list of all the other declas{ which works fine} and then everything shows however if you reload the page/open it from a link nothing shows up in the following fields even when it does get the decla using using the useEffect hoop(at least it logs it out to the console)
TLDR;
when component called/opened from a list it works and loads all the data but when the page is reloaded it is all lost and doesnt load it again with useState
ive taken the liberty to reduces the amount of data show as it is a pretty long file already.
import { useContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import Select from "react-select";
import CreatableSelect from "react-select/creatable";
import DeclaContext from "../../context/DeclaContext";
import LedenContext from "../../context/LedenContext";
import EventContext from "../../context/EventContext";
export const DeclaFrom = () => {
let { id } = useParams();
const navigate = useNavigate();
const { user, leden } = useContext(LedenContext);
const { events } = useContext(EventContext);
let { declas, GET, GET_decla, POST, PUT, DELETE, boekstuks, POST_boekstuk } =
useContext(DeclaContext);
// useEffect(() => {
// const get = async () => {
// await GET();
// const data = await GET_decla(id);
// setDecla(data);
// };
// get();
// // eslint-disable-next-line
// }, [id]);
console.log(declas?.find((de) => (de.id === id ? true : false)));
const [decla, setDecla] = useState(declas?.find((de) => de.id === id));
const [event, setEvent] = useState(decla && decla?.event);
const [owner, setOwner] = useState(decla ? decla?.owner : user.lid_id);
const [content, setContent] = useState(decla ? decla?.content : "");
const [total, setTotal] = useState(decla ? decla?.total : 0);
const [receipt, setReceipt] = useState(decla && decla?.receipt);
const [boekstuk, setBoekstuk] = useState(decla ? decla?.boekstuk : "");
const [content_ficus, setContent_ficus] = useState(
decla ? decla?.content_ficus : ""
);
const optionsLeden = [
{ label: "Select All", value: "all" },
...leden?.map((lid) => ({
value: lid.id,
label: lid?.initials,
})),
];
const [defaultValues, setDefaultValues] = useState(
decla
? decla?.present?.map((pres) =>
optionsLeden?.find((x) => x.value === pres)
)
: []
);
const optionsBoekstuk = boekstuks?.map((boekstuk) => ({
value: boekstuk?.id,
label: boekstuk?.name,
}));
const optionsEvents = events?.map((event) => ({
value: event.id,
label: event.description + " " + event.start_date,
}));
async function createBookstuk(inputValue) {
const BSid = await POST_boekstuk({ name: inputValue });
setBoekstuk(optionsBoekstuk?.find((x) => x.id === BSid)?.value);
}
const onDelete = (e) => {
e.preventDefault();
DELETE({
id,
});
setDeleted(true);
};
const onSubmit = (e) => {
// if (!event | !content | !total | !present) {
// alert("Je moet een evenement kiezen");
// return;
// }
e.preventDefault();
if (decla) {
PUT({
id,
event,
content,
total,
receipt,
boekstuk,
});
navigate("/declas");
} else {
POST({
event,
content,
total,
receipt,
boekstuk,
});
}
setDeleted(false);
};
return (
<div className="columns">
<div className="column is-half is-offset-3">
<form>
<table>
<tbody>
{user.roles.includes("Fiscus") && (
<tr>
<th>
<label htmlFor="id_lid">Lid:</label>
</th>
<td className="field">
<Select
defaultValue={optionsLeden?.find(
(x) => x.value === owner
)}
options={optionsLeden?.filter(
(x) => !["all", 19900].includes(x.value)
)}
name="owner"
id="id_present"
onChange={(e) => {
setOwner(e.value);
}}
/>
</td>
</tr>
)}
<tr>
<th>
<label htmlFor="id_event">Event:</label>
</th>
<td className="field">
<Select
defaultValue={optionsEvents?.find(
(x) => x.value === event?.id
)}
options={optionsEvents}
name="event"
onChange={(e) => {
setEvent(e.value);
}}
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_total">Total:</label>
</th>
<td className="field">
<input
type="number"
onChange={(e) => setTotal(e.target.value)}
name="total"
value={total}
step="any"
className="input"
required
id="id_total"
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_present">Present:</label>
</th>
<Select
isMulti
value={defaultValues}
options={optionsLeden}
required
name="present"
id="id_present"
onChange={(e) => {
if (e.some((val) => val.value === "all")) {
setPresent(
optionsLeden
?.filter((x) => x.value !== "all")
.map((x) => x.value !== "all" && x.value)
); // change the value going to the API
setDefaultValues(
optionsLeden
?.filter((x) => x.value !== "all")
.map((x) => x.value !== "all" && x)
); // change the values displayed
} else {
setPresent(e?.map((x) => x.value)); // change the value going to the API
setDefaultValues(e?.map((x) => x)); // change the values displayed
}
}}
/>
</tr>
<tr>
<th>
<label htmlFor="id_receipt">Receipt:</label>
</th>
<td className="field">
<input
type="file"
name="receipt"
required
accept="image/*"
onChange={(e) => {
setReceipt(e.target.files[0]);
}}
className="input"
id="id_receipt"
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_boekstuk">Boekstuk:</label>
</th>
<td className="field">
<CreatableSelect
defaultValue={optionsBoekstuk?.find(
(x) => x.value === boekstuk
)}
options={optionsBoekstuk}
name="boekstuk"
id="id_boekstuk"
onCreateOption={createBookstuk}
onChange={(e) => {
setBoekstuk(e.value);
}}
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_content_ficus">Content ficus:</label>
</th>
<td className="field">
<textarea
onChange={(e) => setContent_ficus(e.target.value)}
name="content_ficus"
value={content_ficus}
cols="40"
rows="10"
maxLength="100"
className="input"
id="id_content_ficus"
></textarea>
</td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
);
};
export default DeclaFrom;
Thanks for your help
I had a similar issue in a recent project. The problem you're facing is that your state is being erased when you reload the page.
In your useEffect hook, remove id from the dependency array (but leave the empty array) so that it will run once on every render. This way when you reload the page useEffect will run your api call and re-populate your state.
useEffect(() => {
const get = async () => {
await GET();
const data = await GET_decla(id);
setDecla(data);
};
get();
}, []);
veel geluk!
useState values will not persist after page reloads: this is a standard behavior of React. After page reload, all your useState values will reset to their default values.
If you need values to persist after refresh, then you will want to use a browser utility like localStorage.
As an example, this is how I would set/get the value:
const stateFromLocalStorage = localStorage.getItem("itemKey")
const [someState, setSomeState] = useState(stateFromLocalStorage || defaultState)
const settingState = (key, value) => {
localStorage.setItem(key, value)
setSomeState(value)
}
I grab the item from localStorage first, and attempt to assign it as the default value of the useState. If the local storage value doesn't exist, then stateFromLocalStorage will be undefined, and it will set defaultState instead because of the logical || operator.
When setting the state, I set it both with the useState setter function, as well as the local storage setItem function. That way local storage and component state will stay in sync.

React.js Functional Component Localhost Problem

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)
}

How can I display changes in component instantly in React?

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.

Error: Too many re-renders when trying to setState

This is my code:
import React, {useState, useEffect} from 'react';
import './App.css';
import {Table, Button, InputGroup, FormControl} from 'react-bootstrap';
import {PonCard} from "./components/PonCard";
function App() {
const [pons, setPons] = useState(null);
const [translations, setTranslations] = useState(null);
const [isInEditMode, setIsInEditMode] = useState(false);
const [inputValue, setInputValue] = useState('');
const [errors, setErrors] = useState([]);
const [translationsToSave, setTranslationsToSave] = useState([]);
const changeIsInEditMode = () => setIsInEditMode(!isInEditMode);
const handleEditButtonClick = (id) => console.log('Edit', id);
const handleDeleteButtonClick = (id) => console.log('Delete', id);
const handleInputChange = (e) => setInputValue(e.target.value);
const handleFetchOnButtonClick = async () => {
const resp = await fetch(`http://localhost:8080/pons/findTranslation/${inputValue}`).then(r => r.json()).catch(e => console.log(e));
if (resp.ok === true) {
setTranslations(resp.resp[0].hits);
setErrors([]);
} else {
setErrors(resp.errors ? resp.errors : ['Something went wrong. check the input']);
}
};
const handleSaveTranslations = async () => {
const resp = await fetch('http://localhost:8080/pons/', {
method: 'POST',
body: JSON.stringify({original: inputValue, translations: translationsToSave}),
mode: 'cors',
headers: {
'Content-Type': 'application/json',
}
}).then(r => r.json())
.catch(e => {
console.log(e);
return {ok: false};
});
setInputValue('');
setTranslations(null);
if (resp.errors) {
setErrors(resp.errors);
}
};
useEffect(() => {
fetch('http://localhost:8080/pons/')
.then(r => r.json())
.then(resp => {
if (resp.ok === true) {
setPons(resp.pons);
} else {
setErrors(resp.errors);
}
})
.catch(e => console.log(e));
}, []);
return (
<div className="App">
<InputGroup className="mb-3">
<FormControl
value={inputValue}
onChange={handleInputChange}
placeholder={inputValue}
/>
</InputGroup>
<div className="mb-3">
<Button onClick={handleFetchOnButtonClick} disabled={inputValue === '' || errors.length > 0}>Translate</Button>
<Button onClick={changeIsInEditMode}>
{isInEditMode ? 'Exit edit mode' : 'Enter edit mode'}
</Button>
<Button disabled={translationsToSave.length === 0} onClick={handleSaveTranslations}>Save translations</Button>
</div>
{errors.length > 0 ? errors.map(e => <div key={e}>{e}</div>) : null}
{
pons && !translations && inputValue === '' ? pons.map(pon => <PonCard key={Math.random()} {...{pon}}/>) : null
}
{
translations ?
<Table striped bordered hover>
<thead>
<tr>
<th>Original</th>
<th>Translation</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{
translations.map(pon => pon.roms.map(rom => rom.arabs.map(arab => arab.translations.map(translation => {
const {source, target} = translation;
return (
<tr key={Math.random()}>
<td><span dangerouslySetInnerHTML={{__html: source}}/></td>
<td><span dangerouslySetInnerHTML={{__html: target}}/></td>
<td>
{
!translationsToSave.includes(target) ?
<Button onClick={() => {
setTranslationsToSave(prev => [...prev, target]);
}}>
Add translation
</Button>
:
<Button
onClick={() => {
setTranslationsToSave((prev) => {
const index = prev.findIndex(elem => elem === target)
return [...prev.slice(0, index), ...prev.slice(index + 1)]
});
}}>
Remove translation
</Button>
}
</td>
</tr>
)
}))))
}
</tbody>
</Table>
: (
<span>No translations</span>
)
}
</div>
);
}
export default App;
PonCard component:
import {Button, Card} from "react-bootstrap";
import React, {useState} from "react";
export const PonCard = ({pon}) => {
const [isFlipped, setIsFlipped] = useState(false);
const handleClick = setIsFlipped(!isFlipped);
return (
<Card style={{width: '18rem'}}>
<Card.Body>
<Card.Title>{pon.original}</Card.Title>
<Card.Text>
{pon.translations.map(translation => (
<div key={Math.random()} dangerouslySetInnerHTML={translation}/>
))}
</Card.Text>
<Button variant="primary" onClick={handleClick}>Show translations</Button>
</Card.Body>
</Card>
)
};
What I'm trying to do is to fetch data on mount. I found that this is the correct way to mimic componentDidMount
useEffect(() => {
fetch('http://localhost:8080/pons/')
.then(r => r.json())
.then(resp => {
if (resp.ok === true) {
setPons(resp.pons);
} else {
setErrors(resp.errors);
}
})
.catch(e => console.log(e));
}, []);
But I get
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
All the time.
106 | .then(r => r.json())
107 | .then(resp => {
108 | if (resp.ok === true) {
> 109 | setPons(resp.pons);
| ^ 110 | } else {
111 | setErrors(resp.errors);
112 | }
It points to the setPons method, which makes no sense, since it's only updated once on mount. What am I missing?
The issue is this line in PonCard:
const handleClick = setIsFlipped(!isFlipped);
Every time PonCard renders, this line will immediately toggle its flipped state, which renders it again and flips it again, and so on. You probably intended to do this instead:
const handleClick = () => setIsFlipped(!isFlipped);
The reason the error message points to setPons is just that that's the first set state that kicked it off. Prior to that, no PonCard was being rendered, and so there was no infinite loop of PonCard renders.

Categories

Resources