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} />
</>
)
}
Related
I wanna make follow/unfollow toggle button, and following / follower list(object in array) will be called seperately from server.
Follower list needs to have both unfollow/follow button status.
When I call follower list, how can I check the IDs of the people who follow me matches the IDs of my following list & reflect in on the button?
example following, follower object in array
[{id: 1, profileImg: xxx},{id: 2, profileImg: xxx},{id: 3, profileImg: xxx}... ]
my code in js below
const { select } = props;
const [choice, setChoice] = useState(select);
const [followingList, setFollowingList] = useState([]);
const [followerList, setFollowerList] = useState([]);
const handleChoice = (e) => {
setChoice(e.target.value);
};
useEffect(() => {
getFollowing()
.then((res) => {
setFollowingList(res);
})
.then(
getFollower().then((res) => {
setFollowerList(res);
}),
);
}, []);
my code in html
<Container onClick={(e) => e.stopPropagation()}>
<TogglebtnContainer>
<ToggleBtn onClick={handleChoice} value="following" choice{choice}>Following</ToggleBtn>
<ToggleBtn onClick={handleChoice} value="follower" choice={choice}>Follower</ToggleBtn>
</TogglebtnContainer>
<FollowContainer>
<Follow>
{choice === 'following'? (followingList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img"><img src={follow.profileImg} alt="UserPic" /> </div>
<div className="follow-name">{follow.nickname}</div>
<FollowBtn key={follow.id}>Unfollow</FollowBtn></div>
);})
: (followerList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img">
<img src={follow.profileImg} alt="UserPic" />
</div>
<div className="follow-name">{follow.nickname}</div>
<FollowBtn key={follow.id}>follow</FollowBtn>
</div>
})}
</Follow>
</FollowContainer>
</Container>
I thought I could check if this IDs matches IDs of my following list and create a new boolean state.
(ex [isFollowing, setIsFollowing = useState(false)) but couldn't find a way.
getFollower().then((res) => {
setFollowerList(res);
To know which followers the user is already following and follow/unfollow followers
short answer, set a flag when loading the data
useEffect(() => {
let isValidScope = true;
const fetchData = async () => {
const followingList = await getFollowing();
if (!isValidScope) { return; }
setFollowingList(followingList);
let followersList = await getFollower();
if (!isValidScope) { return; }
const followingUserIds = followingList?.map(f => f.id)
followersList = followersList?.map(follower => {
return followingUserIds?.includes(follower.id) ?
{ ...follower, isFollowing: true } : follower
}
setFollowerList(followersList)
}
fetchData()
return () => { isValidScope = false }
}, []);
const onFollowFollower = (followerId) => {
const followersList = followerList?.map(follower => {
return follower.id === followerId ?
{ ...follower, isFollowing: true } : follower
}
setFollowerList(followersList)
}
const onUnfollowFollower = (followerId) => {
const followersList = followerList?.map(follower => {
return follower.id === followerId ?
{ ...follower, isFollowing: false } : follower
}
setFollowerList(followersList)
}
Render code
<Follow>
{choice === 'following'? (followingList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img"><img src={follow.profileImg} alt="UserPic" /> </div>
<div className="follow-name">{follow.nickname}</div>
<FollowBtn key={follow.id}>Unfollow</FollowBtn>
</div>
);})
: (followerList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img">
<img src={follow.profileImg} alt="UserPic" />
</div>
<div className="follow-name">{follow.nickname}</div>
{ follow?.isFollowing ? <FollowBtn () => onUnfollowFollower(follow.id)>Unfollow</FollowBtn> : null }
{ !follow?.isFollowing ? <FollowBtn onClick={() => onFollowFollower(follow.id)>Follow</FollowBtn> : null }
</div>
})}
</Follow>
You can read about working with list in the new React docs
if you are refetching the follower and following list on every change it will be better to recalculate the followers list using a useMemo on every change
Hope this helps you in someway
I am making a mern ecommerce website i just want to see how useEffect works so i console.log in some part of useEffect and loadFilteredResults i saw that --->
initial
entry
skip
entry1
screen shot
but i think it shoud be-->
initial
entry
entry1
skip
why console give this?? i am a begginer, i am a self learner , so please if you need any extra info please comment.
code snippet-->
const loadFilteredResults = (newFilters) => {
console.log("entry")
getFilteredProducts(skip, limit, newFilters).then((data) => {
console.log("entry1")
if (data.error) {
setError(data.error);
} else {
//console.log(data);
setFilteredResults(data.data);
//console.log("size-->");
//console.log(data.size);
setSize(data.size);
setSkip(0);
}
});
};
....
....
useEffect(() => {
init();
console.log("initial");
loadFilteredResults(skip, limit, myFilters.filters);
console.log("skip");
}, []);
//full code of shop.js
import React, { useEffect, useState } from "react";
import Layout from "./Layout";
import Card from "./Card";
import { getCategories, getFilteredProducts } from "./apiCore";
import Checkbox from "./Checkbox";
import RadioBox from "./RadioBox";
import { prices } from "./fixedPrices";
const Shop = () => {
const [myFilters, setMyFilters] = useState({
filters: { category: [], price: [] }
});
const [categories, setCategories] = useState([]);
const [error, setError] = useState(false);
const [limit, setLimit] = useState(3);//prpduct lesss so use 3 but sir used 6
const [skip, setSkip] = useState(0);
const [size, setSize] = useState(0);
const [filteredResults, setFilteredResults] = useState([]);
const init = () => {
getCategories().then((data) => {
if (data.error) {
//console.log("error");
setError(data.error);
} else {
//console.log("set");
//console.log(data);
setCategories(data);
//console.log(data);
}
});
};
const loadFilteredResults = (newFilters) => {
//console.log(newFilters);
console.log("entry")
getFilteredProducts(skip, limit, newFilters).then((data) => {
console.log("entry1")
if (data.error) {
setError(data.error);
} else {
//console.log(data);
setFilteredResults(data.data);
//console.log("size-->");
//console.log(data.size);
setSize(data.size);
setSkip(0);
}
});
};
const loadMore = () => {
console.log("skip"+skip);
console.log("limit"+limit);
let toSkip = skip + limit;
console.log("toSkip"+toSkip);
getFilteredProducts(toSkip, limit, myFilters.filters).then((data) => {
//console.log("filter");
//console.log( myFilters.filters)
if (data.error) {
setError(data.error);
} else {
//console.log(filteredResults);
//console.log(data.data);
setFilteredResults([...filteredResults, ...data.data]);
//console.log("after");
//console.log(...filteredResults);
//console.log(filteredResults);
//console.log(filteredResults);
//console.log([...filteredResults])
//console.log([...filteredResults, ...data.data])
setSize(data.size);
setSkip(toSkip);
}
});
};
const loadMoreButton = () => {
return (
size > 0 &&
size >= limit && (
<button onClick={loadMore} className="btn btn-warning mb-5">
load more
</button>
)
);
};
useEffect(() => {
init();
//console.log(skip);
console.log("initial");
loadFilteredResults(skip, limit, myFilters.filters);
console.log("skip");
}, []);
const handleFilters = (filters, filterBy) => {
//console.log("SHOP", filters, filterBy);
const newFilters = { ...myFilters };
//console.log(newFilters);
newFilters.filters[filterBy] = filters;
//console.log(typeof(filters));
if (filterBy === "price") {
let priceValues = handlePrice(filters);
newFilters.filters[filterBy] = priceValues;
//console.log(priceValues);
}
//console.log(myFilters.filters);
loadFilteredResults(myFilters.filters);
setMyFilters(newFilters);
};
const handlePrice = (value) => {
const data = prices;
let array = [];
//console.log(value);
for (let key in data) {
if (data[key]._id === parseInt(value)) {
array = data[key].array;
}
}
return array;
};
// const x = (filters)=>{
// console.log("filters:"+filters);
// handleFilters(filters, "category")
// }
return (
<Layout
title="Shop Page"
description="search and buy books of your choice"
className="container-fluid"
>
<div className="row">
<div className="col-4">
<h4>Filter by categories</h4>
<ul>
{/* below will be show in list show we wrap it in unorder list */}
<Checkbox
categories={categories}
handleFilters={(filters) =>
handleFilters(filters, "category")
}
/>
</ul>
<h4>Filter by price range</h4>
<div>
<RadioBox
prices={prices}
handleFilters={(filters) => handleFilters(filters, "price")}
/>
</div>
</div>
<div className="col-8">
<h2 className="mb-4">Products</h2>
<div className="row">
{filteredResults.map((product, i) => (
<Card key={i} product={product} />
))}
</div>
<hr />
{loadMoreButton()}
</div>
</div>
</Layout>
);
};
export default Shop;
getFilteredProducts must be a Promise. Please read Using promises
Callbacks added with then() will never be invoked before the
completion of the current run of the JavaScript event loop.
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.
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]);
I am learning to alter data in the backend, json server. The data gets posted if I refresh the server but the render of the new list disappears from the screen after the submit button. Why did the displaying of the notes stop after I submitted a new note ?
const Note = ({note, toggleImportance}) => {
const label = note.important ? 'make not important' : 'make important'
return(
<li >
{note.content}
<button onClick = {toggleImportance}>{label}</button>
</li>
)
}
export default Note
const App = () =>{
const [notes,setNotes] = useState([])
const [newNote, setNewNote] = useState('')
const [showAll, setShowAll] = useState(true)
useEffect(() => {
console.log('effect')
axios
.get('http://localhost:3002/notes')
.then(response => {
console.log('promise fulfilled')
setNotes(response.data)
})
},[])
console.log('render', notes.length, 'notes')
const toggleImportanceOf = id => {
const url = `http://localhost:3002/notes/${id}`
const note = notes.find(n => n.id === id)
const changedNote = {...note, important : !note.important}
axios
.put(url,changedNote)
.then(response => {
setNotes(notes.map(note => note.id !== id ? note : response.data))
})
}
const addNote =(event) =>{
event.preventDefault()
const newObject = {
content: newNote,
date: new Date().toISOString(),
important: Math.random() > 0.5
}
axios
.post('http://localhost:3002/notes', newObject)
.then(response => {
setNotes(notes.concat(response.data))
setNewNote('')
})
}
const handleNoteChange = (event) => {
setNewNote(event.target.value)
}
const notesToShow = showAll ? notes : notes.filter(note => note.important)
const rows = () => {
notesToShow.map(note =>
<Note
key={note.id}
note={note}
toggleImportance = {() => toggleImportanceOf(note.id)}
/>)
}
return(
<div>
<h1>Notes</h1>
<div>
<button onClick = {() => setShowAll(!showAll)}>
show{showAll ? ' important' : ' all'}
</button>
</div>
<ul>
{rows()}
</ul>
<form onSubmit={addNote}>
<input value={newNote} onChange={handleNoteChange}/>
<button type="submit">save</button>
</form>
</div>
)
}
export default App;
How do I fix this ? I want the new notes list to appeaar on the screen but it doesn't.
I forgot to enclose <Note /> in the rows() function with return()