React - Set loading state only on specific clicked button - reactjs

Here is my current code
class cart extends Component {
state = { loading: [] };
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading[e] = true;
this.setState({
loading,
})
};
render() {
const { data } = this.props;
return (
<div>
{data.map(catering => {
const { menus } = catering;
return (
<Row>
{menus.map(menu => (
<Col xs={12} md={12} lg={6} key={menu.id} className="m-bottom-15">
<Card style={{ height: '165px', border: 0, overflow: 'hidden' }}>
<CardActions>
<LoadingButton
className="button small primary pull-right"
loading={thi.state.loading || false}
onClick={e => this.addToCart(e, menu.id)}
>
<MdAdd size={18} color={white} />
<span className="font-14 font-400">Add to cart</span>
</LoadingButton>
</CardActions>
</Card>
</Col>
))}
</Row>
);
}
}
There will be around 20 button when the map function is done.
What I want to achieve is: every time users click add to cart button, I will call the ajax to save the cart and show loading for the specific clicked button.
After ajax is done, return the state back to normal.
On my current code I haven't put my ajax call yet, I still want to make sure the loading button work on press. Right now its not working.
How can I achieve this?
Thanks.

There is some error in your addToCart method. You should use id as index of loading and set loading array to state as this:
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading[id] = true;
this.setState({
loading: loading
});
};
Also, in your render method, change this.state.loading to this.state.loading[menu.id]:
<LoadingButton
className="button small primary pull-right"
loading={this.state.loading[menu.id] || false}
onClick={e => this.addToCart(e, menu.id)}
>
When ajax call is done, you just call setState function which sets loading array values to false in callback method.

Instead of maintaining bool in loading array, maintain the menu ids, whenever Add button is clicked, push the id of that item into loading array. When generating the card check whether loading array have that id or not, if yes then show loading otherwise show the card.
Write it like this:
class cart extends Component {
state = { loading: []};
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading.push(id);
this.setState({
loading
})
};
render() {
const { data } = this.props;
return (
<div>
{data.map((catering,i) => {
const { menus } = catering;
return (
<Row>
{menus.map(menu => {
return this.state.loading.indexOf(menu.id) >= 0 ?
<Col xs={12} md={12} lg={6} key={menu.id} className="m-bottom-15">
<Card style={{ height: '165px', border: 0, overflow: 'hidden' }}>
<CardActions>
<LoadingButton
className="button small primary pull-right"
loading={thi.state.loading || false}
onClick={e => this.addToCart(e, menu.id)}
>
<MdAdd size={18} color={white} />
<span className="font-14 font-400">Add to cart</span>
</LoadingButton>
</CardActions>
</Card>
</Col>
:
/*Loading icon*/
})}
</Row>
)
})}
)
}
}

for the hooks guys this is what I did
const [isLoading, setIsLoading] = useState([]);
<button
type="button"
disabled={isLoading[item.username]}
className="btn btn-success btn-rounded btn-sm my-0"
onClick={()=>activateOrDeactivate( item.activated,item.username)}
> {isLoading[item.username] && <i className="fa fa-circle-o-notch fa-spin"></i>}
{isLoading[item.username]&& <span> Activating...</span>}
{!isLoading[item.username]&& <span>Activate</span>}</button>
const activateOrDeactivate = (valTrueOrFalse,newusername) => {
let loading = isLoading.slice();
loading[newusername] = true;
setIsLoading(loading)
let config = {
headers: {
Authorization: "Bearer " + state.token
},
};
api.post(
"process-activation",
{
activated: !valTrueOrFalse,
username:newusername
},
config
).then(
response => {
getAllUsers()
let loading = isLoading.slice();
loading[newusername] = false;
setIsLoading(loading)

Related

Parsing in props value from parent to component and display

Still new to ReactJS.
I have 3 JS pages: Home, Create, Edit.
From Home, you can navigate to the Create and Edit pages.
You can access the Create page anytime, but you need to call an API to populate some data before you can access the Edit page.
All 3 pages are using the same component, FormEntry. As its name, it generates basically a form input. Within this component, there are 2 functions: Search and AddEdit. Home is using the former, Create and Edit are using the later.
The flow is as such where when you click on the Create button, this will direct you to the Create page. The Create page will then display the form.
However, if you click on the Search button, this will call an API and generate data in a table. Each table row is clickable and clicking on them will direct you to the Edit page. For reusability, I parse in some values using props that, in theory, should populate the form fields based on which row I clicked on.
The issue I'm having is that though the value gets parsed in, the form field is not displaying the correct data. To be specific, the data from the responseData I parsed into the component is not displaying. And even if it does display, it's returning as 'undefined'.
What am I doing wrong?
Home.js
function HomePage() {
const [responseData, setData] = useState([]);
const navigateData = useNavigate();
function navigateToEdit(event){
navigateData({insert URL here}+event.id);
}
function getSearchData2(allData){
( allData.propRefNum !== "" ||
allData.client !== "" ||
allData.appSys !== "" ||
allData.status !== "" ? AxiosCall.searchProposal(JSON.stringify(allData)): AxiosCall.getProposals()
)
.then(
(result) => {
setData(result.data);
}
);
}
return (
<>
<div style={{ height: 400, width: '100%' }}>
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ flexGrow: 1 }}>
<DataGrid onRowClick={navigateToEdit} rows={dataRowObjs} columns={dataColObjs} headerAlign="center" disableColumnFilter />
</div>
</div>
</div>
</>
);
}
export default HomePage;
Edit.js
function EditPage() {
const [responseData, setData] = useState([]);
const { id } = useParams();
useEffect(() => {
const apiData = {
id: id
}
AxiosCall.getProposal(JSON.stringify(apiData))
.then(
(result) => {
setData(result.data);
}
);
},[]);
function getEditData(allData){
fetch({insert URL here}).then(
(result) => {
setData(result);
});
}
return <FormEntry.AddEditFormEntry title="Edit Proposal" defaultDataValue={responseData} responseInputData={getEditData} />
}
export default EditPage;
FormEntry component; AddEditForm
function AddEditFormEntry(props){
const propRefNumRef = useRef();
const descRef = useRef();
const clientRef = useRef();
const appSysRef = useRef();
const statusRef = useRef();
const remarkRef = useRef();
const vendorRef = useRef();
const { register, formState: { errors }, handleSubmit } = useForm();
function submitData(data){
//event.preventDefault();
const propRefNum = propRefNumRef.current.value;
const desc = descRef.current.value;
const client = clientRef.current.value;
const appSys = appSysRef.current.value;
const status = statusRef.current.value;
const remark = remarkRef.current.value;
const vendor = vendorRef.current.value;
const allData = {
propRefNum: propRefNum,
desc: desc,
client: client,
appSys: appSys,
status: status,
remark: remark,
vendor: vendor,
}
props.responseInputData(allData);
}
let defaultRefNum = props.defaultDataValue?.refNum; - **Note A: this is the line in question. When I console.log this variable, it displays the data correctly**
return(
<>
<form className="formEntry" onSubmit={handleSubmit(submitData)}>
<div style={{ display: 'flex'}} >
<div align="left" >
<RouterLink to={insert URL here} >
<IconButton aria-label="search" color="primary" >
<SkipPreviousIcon />
</IconButton>
</RouterLink >
</div>
<h1 style={{ flexGrow: 1, marginTop: -4 }} >{props.title}</h1>
<div align="right">
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
</div>
</div>
<br/>
<Stack justifyContent="center" direction="row" spacing={2} >
<Stack justifyContent="center" direction="column" spacing={2} >
<FieldEntry.TextEntry required="true" label="Proposal Reference Number" type="text" id="input_propRefNum" name="propRefNum" propsRef={propRefNumRef} value={defaultRefNum} />
**Referring to Note A above, I want to populate this field above. I am getting 'undefined', if not blank. If I am using the Create function, blank/undefined is expected. The Edit function is supposed to populate something here **
<FieldEntry.TextEntry label="Description" type="text" id="input_desc" name="desc" propsRef={descRef} value={props.defaultDataValue?.description} />
<FieldEntry.TextEntry required="true" label="Client" type="text" id="input_client" name="client" propsRef={clientRef} />
<FieldEntry.TextEntry required="true" label="Application System" type="text" id="input_appSys" name="appSys" propsRef={appSysRef} value={props.defaultDataValue?.appSystem} />
</Stack>
</Stack>
<br/>
</form>
</>
)
}
Note: I've removed a number of codes that does not pertain to the matter, to keep the sample code small. Rest assured that aside from my issue, everything is working as expected
I think a simpler example(which I just found out will have the same issue) would be
const [defaultRefNum, setRefNum] = useState("");
const [counter, setCounter] = useState(0);
let testValue = props.defaultDataValue?.refNum
useEffect(() => {
console.log("testValue2:",testValue)
if (props.defaultDataValue != null){
console.log("is not Null")
setCounter(c => c+1);
}
else{
console.log("is Null")
}
},[testValue]);
console.log("counter:",counter)
and
<FieldEntry.TextEntry value={counter} />
console.log output - counter: 2
Value in TextEntry: 0
I would assume the value in TextEntry should've outputted to be 2.

how to stop InView in react-intersection-observer

I use inView in my component. Until the posts were loaded, the user see the skeleton. But when I press delete button, the skeleton appears and dissappers. Can I stop this quick appearance when press the delete button.
class MyPosts extends Component {
deleteItem = (key) => {
this.props.deleteItem(key);
};
render() {
const { myPosts, classes } = this.props;
let posts = myPosts.map((item) => {
return (
<InView threshold={0}>
{({ ref, inView }) => (
<div ref={ref} inView={inView}>
{inView ? (
<Card>
<Typography >
{item.post}
</Typography>
<Button
onClick={() => this.deleteItem(item.key)}
>
Done
</Button>
</Card>
) : (
<Skeleton />
)}
</div>
)}
</InView>
);
});
return <div>{posts}</div>;
}
}

Render Items from useState Hook React

I have made a call to my api using useEffect and stored the array of items using useState hook but I'm finding it difficult to render those items into a custom component which will also have the data passed.
Here's my react snippets:
export default function CreateCast() {
const [open, setOpen] = useState(false);
const [bibleCastItems, setBibleCastItems] = useState([]);
const classes = useStyles();
const fabStyle = {
bottom: 50.0,
right: 30.0,
position: "fixed"
};
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
// console.log(items);
// console.log(items.data.bibleCasts);
setBibleCastItems([items.data.bibleCasts]);
// items.data.bibleCasts.length > 0 ? setBibleCastItems([items.data.bibleCasts])
// : setBibleCastItems([]);
}
fetchData();
}, []
);
// console.log('bibleCastItems length ' + bibleCastItems.length);
return (
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card plain>
<CardHeader plain color="primary">
<div className={classes.container}>
<div className={classes.left}>
<h4 className={classes.cardTitleWhite}>All BibleCasts</h4>
<p className={classes.cardCategoryWhite}>
Powered by our friends from <b>Unicorn Tech Consultants</b>{" "}
</p>
</div>
</div>
</CardHeader>
<CardBody>
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item[index]}/>) // this is where I'm facing issue
// bibleCastItems.map((item, index) => {
// console.log(item);
// setMyItem(item);
// return <div key={index}>{index}</div>
// })
}
<div className={classes.right}>
<Fab style={fabStyle} onClick={handleClickOpen}>
<AddIcon />
</Fab>
<UploadFormDialog
open={open}
handleClose={handleClose}
/>
</div>
</CardBody>
</Card>
</GridItem>
</GridContainer>
);
function handleClickOpen(){
setOpen(true);
};
function handleClose(){
setOpen(false);
};
}
Here's my state from browser view:
[![state view][1]][1]
How do I map this state to be a list of components? I'm confused about it
As you can see, I'm using a dialog to create new items and close the dialog once the request is successful. I have one doubt here, how do I tell the main component hosting the dialog that the new data has been fetched and should be added to the state?
My main question here is how to map the items in state to return a list of <CastItem /> component
CastItem Component Snippet
export default function CastItem(props) {
let {bibleCast} = props;
const classes = useStyles();
return <GridContainer>
<GridItem xs={12} sm={6} md={4}>
<Card>
<CardHeader color="info" stats icon>
<CardIcon color="info">
<Streams />
</CardIcon>
</CardHeader>
<CardBody>
<h3 className={classes.cardTitle}>{bibleCast.title}</h3>
<p className={classes.cardCategory}> Reinhard Bonnke</p>
</CardBody>
</Card>
</GridItem>
</GridContainer>
}
CastItem.propTypes = {
bibleCast: PropTypes.object.isRequired,
}
JSON Response from API in console:
[![json response][2]][2]
If you were to create a state variable to represent this response as a list and display that list, how would you go about it, using hooks. Thank you.
[1]: https://i.stack.imgur.com/QkthN.png
[2]: https://i.stack.imgur.com/8Hf11.png
Mistake you are doing is in CreateCast component , form api you are already getting an array again you are passing it inside an array, so it is coming as nested array
Do like this
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
setBibleCastItems(items.data.bibleCasts);
}
fetchData();
}, []
);
For Maping do like this
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item}/>)
}
// For question how to update parent from child follow below
There are two ways you can set data in a parent component , one is refetch from the api or pass from children to parent and update the state there
I have an example here how to update parent and children,to add names to a list,name list state is maintained in parent component here and child will pass back value to parent by adding name
import { useState } from "react";
import Child from "./Child";
export default function Parent() {
const [list, setList] = useState(["ram"]);
const handleAddName = (name) => {
if (name) {
setList([name, ...list]);
// or you can refetch the list from api here
}
};
return (
<div>
<div style={{ float: "left" }}>
<h1>I am a parent Component</h1>
<ul>
{list &&
list.map((item) => {
return <li key={item}>{item}</li>;
})}
</ul>
</div>
<Child handleSubmit={handleAddName} />
</div>
);
}
Child
import { useState } from "react";
export default function Child(props) {
const [name, setName] = useState("");
const updateNameList = (name) => {
if (name) {
props.handleSubmit(name);
//reset field after data is sent
// you can also save data here making post request respective api
setName("");
}
};
return (
<div style={{ float: "right" }}>
<h1>I am a Child Component</h1>
<p> Add names below</p>
<br />
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => updateNameList(name)}>Add</button>
</div>
);
}
refer to this codesand box

App keeps fetching firebase data with button click [ReactJs] (CLOSED)

I have an online restaurant app that fetches the menu items from firebase. It fetches everytime I add something to the cart, which makes the menu items disappear and then reappear for a second. It also scrolls back to the top of the page after every click. How do I prevent this reload? e.preventDefault() doesnt work. Is it due to the passing of data from the child to the parent? I'm not sure.
//imports
import React, { useState, useEffect } from "react";
const Menue = (props) => {
const [cartLength, setCartLength] = useState(0);
const [indischeGerichte, setIndischeGerichte] = useState([])
useEffect(() => {
fire.firestore().collection("Indische Gerichte")
.orderBy("id", "asc")
.get()
.then(snapshot => {
var ind = []
snapshot.forEach(doc => {
ind.push(doc.data())
})
setIndischeGerichte(ind)
}).catch(error => {
console.log(error)
});
}, [])
function addToCart(e, item) {
e.preventDefault();
var updatedCart = { ...props.cart };
if (!updatedCart[item.title]) {
updatedCart[item.title] = [1, item.price];
} else {
updatedCart[item.title][0]++;
}
setCartLength(cartLength + 1);
props.setTheCart(updatedCart, cartLength);
}
return (
<div>
<Typography variant="h3" component="h2" gutterBottom>
Speisekarte
</Typography>
<div id="ind">
<Typography variant="h4">Indian Foods:</Typography>
{indischeGerichte.map((indFood, idx) => {
return (
<div key={idx}>
<Card className="foodCard">
<Typography variant="h4">{indFood.title}</Typography>
<Button
variant="contained"
color="secondary"
onClick={(e) => addToCart(e, indFood)}
>
1x In den Einkaufswagen
</Button>
</Card>
</div>
);
})}
</div>
</div>
);
};
export default Menue;

working with user blocking system in react

I am making a web app like Tinder. Currently, I am working with user blocking. I have a "blocked" table in the database. In my application, A user already can block another user. Now, I want to hide the users in the search result if the logged user already blocked them.
Here is the code I have. This code renders for infinity (it doesn't stop). Can anyone tell me how can I make it working?
const ListOfUsers = ({ users }) => {
//console.log('users list rendered', users)
var coords = JSON.parse(window.localStorage.getItem('loggedMatchaUser'));
var from_user_id = coords.user_id;
const [check, setCheck] = useState(0)
const checkBlock = (to_user_id) => {
blockService.blockedUser({from_user_id, to_user_id})
.then(res => {
//If this row exist in the table it return 1 otherwise 0
setCheck(res.value);
})
.catch(e => {
console.log(("Error: couldn't get block info"))
})
}
return (
users && users.length > 0
? <ListGroup className="text-left" variant="flush">
{users.map(u => <Link to={`/users/${u.user_id}`} key={u.user_id}>
{checkBlock(u.user_id)}
{!check &&
<ListGroup.Item>
<div style={{display: "inline-block", width: "60%"}}>{u.username}, {u.age.years}</div>
<div style={{display: "inline-block", width: "40%", textAlign: "right"}}>{parseInt(u.distance)} km<br />
<FontAwesomeIcon icon={faAward} /> {u.fame}</div>
</ListGroup.Item>
}
</Link>)}
</ListGroup>
: <div className="text-info">Could not find any matching users<br />please try different filters</div>
)
}
You are issuing a side-effect (i.e. calling a function that updates state) in the render return.
const ListOfUsers = ({ users }) => {
...
const [check, setCheck] = useState(0);
const checkBlock = (to_user_id) => {
blockService
.blockedUser({ from_user_id, to_user_id })
.then((res) => {
//If this row exist in the table it return 1 otherwise 0
setCheck(res.value); // <-- updates state
})
.catch((e) => {
console.log("Error: couldn't get block info");
});
};
return users && users.length > 0 ? (
<ListGroup className="text-left" variant="flush">
{users.map((u) => (
<Link to={`/users/${u.user_id}`} key={u.user_id}>
{checkBlock(u.user_id)} // <-- function call updates state
...
</Link>
))}
</ListGroup>
) : (
...
);
};
You'll likely want to use an effect hook to check blocked users when the users prop updates. You'll need to "preprocess" your users array and augment it with the blocked status, or simply filter them out, which is probably easier anyway. The reason is because you can't use a single check state value for every user being rendered.
const ListOfUsers = ({ users }) => {
const coords = JSON.parse(window.localStorage.getItem("loggedMatchaUser"));
const from_user_id = coords.user_id;
const [nonBlockedUsers, setNonBlockedUsers] = useState([]);
useEffect(() => {
const checkBlock = (to_user_id) =>
// If this row exist in the table it return 1 otherwise 0
blockService.blockedUser({ from_user_id, to_user_id });
Promise.all(users.map(({ user_id }) => checkBlock(user_id))).then(
(blockedUserStatus) => {
const nonBlockedUsers = [];
users.forEach((user, index) => {
if (!blockedUserStatus[index]) nonBlockedUsers.push(user);
});
setNonBlockedUsers(nonBlockedUsers);
}
);
}, [from_user_id, users]);
return nonBlockedUsers.length > 0 ? (
<ListGroup className="text-left" variant="flush">
{nonBlockedUsers.map((u) => (
<Link to={`/users/${u.user_id}`} key={u.user_id}>
<ListGroup.Item>
<div style={{ display: "inline-block", width: "60%" }}>
{u.username}, {u.age.years}
</div>
<div
style={{
display: "inline-block",
width: "40%",
textAlign: "right"
}}
>
{parseInt(u.distance)} km
<br />
<FontAwesomeIcon icon={faAward} /> {u.fame}
</div>
</ListGroup.Item>
</Link>
))}
</ListGroup>
) : (
<div className="text-info">
Could not find any matching users
<br />
please try different filters
</div>
);
};

Resources