React : Maximum update depth exceeded - reactjs

Still with my react project, now I'm learning the hooks, perhaps I have an issue with the 'infinite loop' (Maximum update depth exceeded) and I can't figure out how to handle this. I have redux to handle the states. I used useEffect, because when I clicked on a div, it was showing, or did what I wanted, always one step late
function OffersWhatTypeOfWebsiteComponent(props) {
const dispatch = useDispatch()
const [active, setActive] = useState(1)
const [typeWebSite, setTypeWebSite] = useState('withCMS')
const updateTypeWebSite = () => {
dispatch({
type: "TYPE_WEBSITE",
payload: typeWebSite
})
}
useEffect(() => {
updateTypeWebSite();
}, [updateTypeWebSite()]);
const renderElements = (props) => {
switch (active) {
case 1 :
return (
<>
<OffersChooseYourPackageCMSComponent
/>
</>
);
break
case 2 :
return (
<>
<OffersChooseYourPackageLikeAProComponent/>
</>
)
default:
return 'error'
}
}
return (
<div>
<OffersTitleCardComponent
titleNumber='2'
titleSection='What type of Website'
/>
<div className='chooseYourProject'>
<OffersCardsWithCheckComponent
titleCard='With CMS'
subtitleCard='xxxx'
active={active === 1 ? 'listing-active' : 'listing'}
onClick={() => {
setActive(1);
setTypeWebSite('withCMS');
updateTypeWebSite()
}}
/>
<OffersCardsWithCheckComponent
titleCard='Like a pro'
subtitleCard='xxx'
active={active === 2 ? 'listing-active' : 'listing'}
onClick={() => {
setActive(2);
setTypeWebSite('like a pro');
updateTypeWebSite()
}}
/>
</div>
{
renderElements({})
}
</div>
);
}
export default OffersWhatTypeOfWebsiteComponent;
This is the sub-component :
function OffersChooseYourPackageCMSComponent(props) {
const dispatch = useDispatch()
const [active, setActive] = useState(1)
const [packageWebSite, setPackageWebSite] = useState('Shopify')
const [pricePackageWebSite, setPricePackageWebSite] = useState(300)
const updatePackageWebSite = () => {
dispatch({
type: "PACKAGE_WEBSITE",
payload: {packageWebSite, pricePackageWebSite}
})
}
useEffect(() => {
updatePackageWebSite();
}, [updatePackageWebSite()]);
const renderElements = () => {
switch (active) {
case 1 :
return (
<>
<OffersNumbersOfProductsComponent/>
</>
);
break
case 2 :
return (
<>
<OffersNumbersOfPagesComponent/>
<OffersWoocommerceComponent/>
</>
);
break
default :
return 'error'
}
}
return (
<div>
<OffersTitleCardComponent
titleNumber='3'
titleSection='Choose your package'
/>
<div className="shopify">
<OffersCardsWithCheckComponent
onClick={() => {
setActive(1);
setPackageWebSite('Shopify');
setPricePackageWebSite(300);
updatePackageWebSite()
}}
active={active === 1 ? "listing-active" : "listing"}
titleCard='Shopify'
subtitleCard='xxx'
pricePackage='$54349'
detailPrice='(1 landing page + up to 5 products)'
/>
<OffersCardsWithCheckComponent
onClick={() => {
setActive(2);
setPackageWebSite('WordPress');
setPricePackageWebSite(900);
updatePackageWebSite()
}}
active={active === 2 ? "listing-active" : "listing"}
titleCard='WordPress'
subtitleCard='xxxx'
pricePackage='$23349'
detailPrice='(1 landing page)'
/>
</div>
{renderElements()}
</div>
);
}
export default OffersChooseYourPackageCMSComponent;
Don't hesitate to tell me some good practices too, on what I should arrange also if needed.
Thanks for your help

You should replicate this into your sub-component as well
const updateTypeWebSite = useCallback(() => {
dispatch({
type: "TYPE_WEBSITE",
payload: typeWebSite
})
}, [typeWebSite])
useEffect(() => updateTypeWebSite(), [updateTypeWebSite]);
Read this at reactjs documentation

Found something that worked, don't know if it's the best solutuion :
const [typeWebSite, setTypeWebSite] = useState('withCMS')
const updateTypeWebSite = () => {
dispatch({
type: "TYPE_WEBSITE",
payload: typeWebSite
})
}
useEffect(() => {
updateTypeWebSite()
},[typeWebSite,setTypeWebSite]);

Related

How to show error message when there is no result result?

I'm working on a note taking app. I would like to show the error message only if there is no search result. The problem is that currently I'm displaying the error message even when I don't have any notes in the array, which is not what I want. What am I doing wrong?
Here's my code and what I've tried so far:
const [notesList, setNotesList] = React.useState<string[]>([]);
const [query, setQuery] = React.useState<string>("");
const addNote = (): void => {
setNotesList([...notesList, ""]);
};
const deleteNote = React.useCallback(
(idx: number): void => {
const newList = [...notesList];
newList.splice(idx, 1);
setNotesList(newList);
},
[notesList]
);
const updateNote = React.useCallback(
(idx: number, text: string): void => {
const newList = [...notesList];
newList.splice(idx, 1, text);
setNotesList(newList);
},
[notesList]
);
React.useEffect(() => {
const storage_string = sessionStorage.getItem(LOCAL_STORAGE_KEY);
if (!storage_string) return;
try {
const savedNotes = JSON.parse(storage_string) as string[];
if (savedNotes) setNotesList(savedNotes);
} catch (err) {
console.log("error");
}
}, []);
React.useEffect(() => {
sessionStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(notesList));
}, [notesList]);
const getFilteredNotes = (notes: string[], query: string) => {
if (!query) {
return notes;
}
return notes.filter((note) => note.includes(query));
};
const filteredNotes = getFilteredNotes(notesList, query);
const updateQuery = (event: React.ChangeEvent<HTMLInputElement>) =>
setQuery(event.target.value);
const handleSearch = debounce(updateQuery, 500);
return (
<div ref={wrapperRef}>
<GlobalStyle />
<SidePanel showSidePanel={showSidePanel}>
<HeaderContainer>
<SearchInput placeholder='Search notes' type='text' onChange={handleSearch} />
<Button variant='icon' onClick={addNote}>
<AddIcon/>
</Button>
</HeaderContainer>
{filteredNotes && filteredNotes.length > 0 ? filteredNotes.map((note: string, idx: number) => (
<Note
onChange={updateNote}
remove={deleteNote}
idx={idx}
text={note}
key={idx}
/>
)) : <Text>No results found</Text>}
</SidePanel>
<ToggleButton variant='primary' onClick={toggleSidePanel}>
<NoteIcon width='2rem' height='2rem'/>
Notes
</ToggleButton>
</div>
);
};
Your condition will only ever render notes, or your fallback text:
{filteredNotes && filteredNotes.length > 0 ? filteredNotes.map((note: string, idx: number) => (
<Note
onChange={updateNote}
remove={deleteNote}
idx={idx}
text={note}
key={idx}
/>
)) : <Text>No results found</Text>}
As #kelly mentioned above, you need to check your notesList and if you don't want to show your fallback text then skip rendering this section.
There's many ways you can do that - abstracting the logic for what to render given different scenarios to a function would be more readable, but you can try this to see if it solves your problem:
{notesList.length > 0 && // <- this is new, and the rest is wrapped in parenthesis ()
(filteredNotes && filteredNotes.length > 0 ? (
filteredNotes.map((note: string, idx: number) => (
<Note
onChange={updateNote}
remove={deleteNote}
idx={idx}
text={note}
key={idx}
/>
))
) : (
<Text>No results found</Text>
))}
If your notesList is empty, it won't render anything

unable to update star rating from react to mongodb

Hello I am stuck in updating the star rating from react js code using axios to mongodb. The stars are getting clicked but the value is not showing neither getting updating.
Here is the front end code:
const StarRating = (props) => {
console.log(props);
return (
<div>
{Array(5)
.fill(0)
.map((_, idx) => (
<label key={idx}>
<input
type="radio"
name="rating"
onChange={handleRate}
value={props.ratingValue}
checked={idx === props.ratingValue}
/>
<FaStar color={idx < 3 ? "#01af93" : "#bbb"} />
</label>
))}
</div>
);
};
const Report = (props) => {
const { advices } = useSelector((state) => state.advice);
const [rate, setRating] = useState(null);
useEffect(() => {
if (!advices) {
dispatch(fetchAdvices(history));
}
});
useEffect(() => {
async function fetchRate() {
try {
const { rating } = await axios.get(http://localhost:8080/rating);
console.log(rating + "user rating");
} catch(error) {
console.log(error);
}
};
fetchRate();
}, []);
const handleRate = async() => {
const rate = await axios.post(http://localhost:8080/rating, {rating:rate, collegeId: collegeId});
props.setRating(rate)
}
return (
<>
<Container>
<Grid>
<Fragment>
<Grid >
<Card>
<CardContent><> <div>{advices[0}.college_name}
<StarRating setRating={(val) => setRate(val)} ratingValue={rate} />
</div></></CardContent>
</Card>
</Grid>
</>
)};
How to update values of rating based on collegeID from react code. The concept here is to update the value of rating based on the college name showed to user from the score he gets. College name is getting displayed but the value of rating is not getting displayed nor getting changed.
Code seems messed up during copy/paste, is this what you are trying to achive?
const StarRating = (props) => {
console.log(props)
// sends rating to database then updates the rating ui
const handleRate = async (ratingToSave) => {
const rate = await axios.post('http://localhost:8080/rating', {
rating: ratingToSave,
// collegeId should come from wherever stored
collegeId: collegeId
})
props.setRating(rate)
}
return (
<div>
{Array(5)
.fill(0)
.map((_, idx) => (
<label key={idx}>
<input
type="radio"
name="rating"
// idx+1 if minimum rating is 1
onChange={() => handleRate(idx + 1)}
value={props.ratingValue}
checked={idx === props.ratingValue}
/>
<FaStar color={idx < 3 ? '#01af93' : '#bbb'} />
</label>
))}
</div>
)
}
const Report = (props) => {
const { advices } = useSelector((state) => state.advice)
const [rate, setRate] = useState(null)
useEffect(() => {
if (!advices) {
dispatch(fetchAdvices(history))
}
// dependency array might need update, test first
}, [advices])
useEffect(() => {
async function fetchRate() {
try {
const { rating } = await axios.get('http://localhost:8080/rating')
console.log(rating + 'user rating')
} catch (error) {
console.log(error)
}
}
fetchRate()
// to rerender and refetch after user sends rating to database
}, [rate])
// jsx implement again
return (
<>
<StarRating setRating={(val) => setRate(val)} ratingValue={rate} />
</>
)
}

React how to sum numbers from a map function?

How can i sum numbers out of a map function?
I'm getting all the prices for each month in form of numbers.
Im getting just single numbers, not an Array from my mapping function so i can't use reduce function
Edit: Posted all code now i hope it helps to understand it better now.
This is what i get when i show carttotal.
It's shown for the correct month but not as a sum
[Current output][1]
const PayFees = () => {
const [history, setHistory] = useState([]);
const token = useSelector((state) => state.token);
useEffect(() => {
const getHistory = async () => {
const res = await axios.get('/api/payment', {
headers: { Authorization: token },
});
setHistory(res.data);
};
getHistory();
}, [token, setHistory]);
const sum = history
.map((order) => order.carttotal)
.reduce((prev, curr) => prev + curr, 0);
const getYears = new Set(
history.map((date) => {
const year = dayjs(date.createdAt).format('YYYY');
return year;
})
);
const getMonths = new Set(
history.map((date) => {
const monthYear = dayjs(date.createdAt).format('MM/YYYY');
return monthYear;
})
);
const yearsArr = [...getYears];
const monthArr = [...getMonths];
return (
<div>
<div>
{yearsArr.map((year) => {
return (
<div>
<div>{year}</div>
<div>
{monthArr.map((month) => {
return dayjs(month, 'MM/YYYY').format('YYYY') === year ? (
<div>
{month}
{history.map((order) => {
console.log(order);
return dayjs(order.createdAt).format('MM/YYYY') ===
month ? (
<div>{order.carttotal}</div>
) : (
''
);
})}
</div>
) : (
''
);
})}
</div>
</div>
);
})}
</div>
</div>
);
};
export default PayFees;
```
[1]: https://i.stack.imgur.com/Qvb5A.png
I'm sure there are other ways, but you could try this:
const [total, setTotal] = useState(0)
{history.map((order) => {
setTotal(total + order.carttotal)}}
Then call {total} wherever you need it.
Maybe its a ugly solution and way to long but im happy i finally found a solution. I crated a new component and with the code below everything works
{yearsArr.map((year) => {
return (
<div>
<div>{year}</div>
<div>
{monthArr.map((month) => {
return dayjs(month, 'MM/YYYY').format('YYYY') === year ? (
<>
{month}
<MonthlyFee date={month} />
</>
) : (
''
);
})}
</div>
</div>
);
})}
MonthlyFee component:
const MonthlyFee = ({ date }) => {
const [history, setHistory] = useState([]);
const token = useSelector((state) => state.token);
useEffect(() => {
const getHistory = async () => {
const res = await axios.get('/api/payment');
setHistory(res.data);
};
getHistory();
}, [token, setHistory]);
const getMonthlyOrder = history.map((order) => {
const monthlyOrder =
date === dayjs(order.createdAt).format('MM/YYYY')
? order
: { carttotal: 0 };
return monthlyOrder;
});
const final = getMonthlyOrder
.map((order) => order.carttotal)
.reduce((prev, curr) => prev + curr, 0);
return <div>{final}</div>;
};
export default MonthlyFee;

Text field should only change for one value and not over the entire list

I have a list and this list has several elements and I iterate over the list. For each list I display two buttons and an input field.
Now I have the following problem: as soon as I write something in a text field, the same value is also entered in the other text fields. However, I only want to change a value in one text field, so the others should not receive this value.
How can I make it so that one text field is for one element and when I write something in this text field, it is not for all the other elements as well?
import React, { useState, useEffect } from 'react'
import axios from 'axios'
function Training({ teamid }) {
const [isTrainingExisting, setIsTrainingExisting] = useState(false);
const [trainingData, setTrainingData] = useState([]);
const [addTraining, setAddTraining] = useState(false);
const [day, setDay] = useState('');
const [from, setFrom] = useState('');
const [until, setUntil] = useState('');
const getTrainingData = () => {
axios
.get(`${process.env.REACT_APP_API_URL}/team/team_training-${teamid}`,
)
.then((res) => {
if (res.status === 200) {
if (typeof res.data !== 'undefined' && res.data.length > 0) {
// the array is defined and has at least one element
setIsTrainingExisting(true)
setTrainingData(res.data)
}
else {
setIsTrainingExisting(false)
}
}
})
.catch((error) => {
//console.log(error);
});
}
useEffect(() => {
getTrainingData();
}, []);
const deleteTraining = (id) => {
axios
.delete(`${process.env.REACT_APP_API_URL}/team/delete/team_training-${teamid}`,
{ data: { trainingsid: `${id}` } })
.then((res) => {
if (res.status === 200) {
var myArray = trainingData.filter(function (obj) {
return obj.trainingsid !== id;
});
//console.log(myArray)
setTrainingData(() => [...myArray]);
}
})
.catch((error) => {
console.log(error);
});
}
const addNewTraining = () => {
setAddTraining(true);
}
const addTrainingNew = () => {
axios
.post(`${process.env.REACT_APP_API_URL}/team/add/team_training-${teamid}`,
{ von: `${from}`, bis: `${until}`, tag: `${day}` })
.then((res) => {
if (res.status === 200) {
setAddTraining(false)
const newTraining = {
trainingsid: res.data,
mannschaftsid: teamid,
von: `${from}`,
bis: `${until}`,
tag: `${day}`
}
setTrainingData(() => [...trainingData, newTraining]);
//console.log(trainingData)
}
})
.catch((error) => {
console.log(error);
});
}
const [editing, setEditing] = useState(null);
const editingTraining = (id) => {
//console.log(id)
setEditing(id);
};
const updateTraining = (trainingsid) => {
}
return (
<div>
{trainingData.map((d, i) => (
<div key={i}>
Trainingszeiten
<input class="input is-normal" type="text" key={ d.trainingsid } value={day} placeholder="Wochentag" onChange={event => setDay(event.target.value)} readOnly={false}></input>
{d.tag} - {d.von} bis {d.bis} Uhr
<button className="button is-danger" onClick={() => deleteTraining(d.trainingsid)}>Löschen</button>
{editing === d.trainingsid ? (
<button className="button is-success" onClick={() => { editingTraining(null); updateTraining(d.trainingsid); }}>Save</button>
) : (
<button className="button is-info" onClick={() => editingTraining(d.trainingsid)}>Edit</button>
)}
<br />
</div>
))}
)
}
export default Training
The reason you see all fields changing is because when you build the input elements while using .map you are probably assigning the same onChange event and using the same state value to provide the value for the input element.
You should correctly manage this information and isolate the elements from their handlers. There are several ways to efficiently manage this with help of either useReducer or some other paradigm of your choice. I will provide a simple example showing the issue vs no issue with a controlled approach,
This is what I suspect you are doing, and this will show the issue. AS you can see, here I use the val to set the value of <input/> and that happens repeatedly for both the items for which we are building the elements,
const dataSource = [{id: '1', value: 'val1'}, {id: '2', value: 'val2'}]
export default function App() {
const [val, setVal]= useState('');
const onTextChange = (event) => {
setVal(event.target.value);
}
return (
<div className="App">
{dataSource.map(x => {
return (
<div key={x.id}>
<input type="text" value={val} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
This is how you would go about it.
export default function App() {
const [data, setData]= useState(dataSource);
const onTextChange = (event) => {
const id = String(event.target.dataset.id);
const val = String(event.target.value);
const match = data.find(x => x.id === id);
const updatedItem = {...match, value: val};
if(match && val){
const updatedArrayData = [...data.filter(x => x.id !== id), updatedItem];
const sortedData = updatedArrayData.sort((a, b) => Number(a.id) - Number(b.id));
console.log(sortedData);
setData(sortedData); // sorting to retain order of elements or else they will jump around
}
}
return (
<div className="App">
{data.map(x => {
return (
<div key={x.id}>
<input data-id={x.id} type="text" value={x.value} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
What im doing here is, finding a way to map an element to its own with the help of an identifier. I have used the data-id attribute for it. I use this value again in the callback to identify the match, update it correctly and update the state again so the re render shows correct values.

Why React hook useState don't increment index onClick?

I don't understand why in setProjectIndex inside SwitchProject function not updating my projectIndex state :
const WorkPreview = ({project, projects}) => {
const [currProject, setCurrProject] = useState(projects[0]);
const [projectIndex, setProjectIndex] = useState(0);
useEffect(() => {
console.log("useEffect idx", projectIndex) // log: 1 when onClick to Right Button
}, [projectIndex])
const SwitchProject = (incrDecrAmount) => {
let nextIndex = projectIndex + incrDecrAmount;
if (nextIndex >= 0 && nextIndex < (projects.length-1)) {
setProjectIndex(projectIndex + incrDecrAmount); // sets 0
console.log("projectIndex", projectIndex) // log: 0 when onClick to Right Button (increment by 1)
console.log("nextIdx", nextIndex) // log: 1 when onClick to Right Button
setCurrProject(projects[projectIndex]);
console.log("", projects[projectIndex]); // gives projects[0] not projects[1]
}
}
return (
<div className="works__preview" id="workPreview">
<button className="works__itemBtn" id="btnfixedLeft" onClick={() => { SwitchProject(-1) }}>
<Icon.ArrowLeft></Icon.ArrowLeft>
</button>
<button className="works__itemBtn" id="btnfixedRight" onClick={() => { SwitchProject(1) }}>
<Icon.ArrowRight></Icon.ArrowRight>
</button>
</div>
)
I've checked other questions and try different ways but gives the same result
Can someone explain me that and gives me the solution ?
Since you are just increasing/decreasing values, in your position I would just utilize a userReducer hook
import React, { useState, useEffect, useReducer } from "react";
const useCounterHook = (state: { count: number }, action: { type: string }) => {
switch (action.type) {
case "increment":
return { count: ++state.count };
case "decrement":
return { count: --state.count };
default:
throw new Error();
}
};
const WorkPreview = ({ project, projects }) => {
const [currProject, setCurrProject] = useState(projects[0]);
const [state, dispatch] = useReducer(useCounterHook, { count: 0 });
useEffect(() => {
console.log("state effect", state.count);
console.log("state next effect", state.count + 1);
setCurrProject(projects[state.count < 0 ? 0 : state.count]);
}, [projects, state]);
useEffect(() => {
console.log("currProject", currProject);
}, [currProject]);
return (
<div className="works__preview" id="workPreview">
<button
className="works__itemBtn"
id="btnfixedLeft"
onClick={() => dispatch({ type: "decrement" })}
>
<Icon.ArrowLeft></Icon.ArrowLeft>
</button>
<button
className="works__itemBtn"
id="btnfixedRight"
onClick={() => dispatch({ type: "increment" })}
>
<Icon.ArrowRight></Icon.ArrowRight>
</button>
</div>
);
};

Resources