How to update state in onSubmit form & send data through components? - javascript

I have a state which I need to update with the ID returned from an endpoint call so I can call another another endpoint using that ID, I've made a state in the parent component and I use it in my first form to set the ID. I pass that id as a prop to the component that needs it but when I console.log the state, it doesn't change.
How can I pass the ID through the components?
I've added comments on the main places to look at
Here is my first form where I need the ID from -
const AddWebAppTypeForm = (props: any, ref: any) => {
const { setWebAppTypes, setNewAppValues}: AddWebAppTypeFormProps =
props;
const {
handleSubmit,
control,
reset,
formState: { isDirty },
} = useForm();
const onSubmit = (data: any) => {
let formData = new FormData();
formData.append("name", data.Title);
formData.append("description", data.Description);
formData.append("uploaded_file", data.Image[0]);
if (isDirty) {
createWebAppType(formData);
}
};
const createWebAppType = async (body: any) => {
await fetch(`${process.env.REACT_APP_API_URL}/webapptype`, {
method: "POST",
body: body,
})
.then((response) => response.json())
.then((data: IWebAppType) => {
const model: IWebAppType = {
id: data.id,
name: data.name,
image: data.image,
description: data.description,
image_url: data.image_url,
};
setNewAppValues(model.id); // <--- Set the state here
setWebAppTypes((prev) =>
prev.map((item) => (item.id === 0 ? model : item))
);
enqueueSnackbar(`Created App Succesfully`, {
variant: "success",
});
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<button hidden={true} ref={ref} type="submit" />
</form>
);
};
export default forwardRef(AddWebAppTypeForm);
My parent component with the states -
function WebAppTypeAccordion({ a, setWebAppTypes }: WebAppTypeAccordionProps) {
const [formEl, setFormEl] = useState(null);
const [addFormEl, setAddFormEl] = useState(null);
const [newAppValues, setNewAppValues] = useState<number>(0); // <--- state with 0 as initialised value
const handleRef = (el: any) => {
if (el !== null) {
setFormEl(el);
}
};
const handleAddRef = (el: any) => {
if (el !== null) {
setAddFormEl(el);
}
};
return (
<Accordion defaultExpanded={a.id === 0}>
<AccordionSummary
// onClick={(e) => handleOnClick(a, e)}
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>{a.name}</Typography>
</AccordionSummary>
<AccordionDetails>
{a.id === 0 ? (
<AddWebAppTypeForm
setWebAppTypes={setWebAppTypes}
ref={handleAddRef}
setNewAppValues={setNewAppValues} // <--- Passing setState to set id
/>
) : (
null
)}
<MappedAccordion
waobj={a}
key={a.id}
setWebAppTypes={setWebAppTypes}
editRef={formEl}
addRef={addFormEl}
newAppValues={newAppValues} // <--- Passing ID
/>
</AccordionDetails>
</Accordion>
);
}
export default WebAppTypeAccordion;
Here is where I am trying to use the ID to call another endpoint
function MappedAccordion({
waobj,
setWebAppTypes,
editRef,
addRef,
newAppValues,
}: MappedAccordionProps) {
const handleCreate = (data: FieldT) => {
let wtype: string = String(waobj.id);
if (addRef !== null) {
if (newAppValues !== 0) {
wtype = String(newAppValues); // <--- Try to use the ID but get default value
createFetch(data, wtype); // <--- Try to use the ID but get default value
}
}
createFetch(data, wtype);
};
const createFetch = (data: FieldT, wtype: string) => {
let formData = new FormData();
formData.append("name", data.name);
formData.append("link", data.link);
formData.append("wtype", wtype);
fetch(`${process.env.REACT_APP_API_URL}/webapp/`, {
method: "POST",
body: formData,
})
.then((response) => {
if (!response.ok) {
let err = new Error("HTTP status code: " + response.status);
enqueueSnackbar(`Environment already exists`, {
variant: "error",
});
throw err;
}
return response.json();
})
.then((data: IWebApp) => {
const model: FieldT = {
wid: data.id,
name: data.name,
link: data.link,
};
enqueueSnackbar(`Created Environment ${model.wid}`, {
variant: "success",
});
});
};
const onSubmit = (data: FormFields) => {
if (addRef !== null) addRef?.click(); // <--- Submit AddWebAppTypeForm form, set the ID
else editRef?.click();
let onSubmitData: FieldT[] = data.myFieldValues;
onSubmitData.map((a: FieldT, index) => {
let originalField: FieldT = initialFields[index];
if (a.wid === undefined) {
handleCreate(a);
} else {
if (JSON.stringify(a) !== JSON.stringify(originalField)) {
handleEdit(a);
}
}
});
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} id="environment-form">
<div style={{ paddingTop: 10 }}>
<Button
type="submit" // <--- Submit form
variant="outlined"
size="small"
sx={{ marginRight: 1 }}
>
Save
</Button>
<Button
variant="outlined"
size="small"
onClick={handleAppDelete}
disabled={waobj.id === 0 ? true : false}
>
Delete
</Button>
</div>
</form>
</div>
);
}
export default MappedAccordion;
Thanks for taking a look, I appreciate any help!

Related

React function renders - subscribeToMore graph-ws

I am building a chat app and using graphQl-ws and Im having issues with a function firing multiple times. My problem is why does doSubscriptionAction fire repeatedly and how to prevent that.
component
const ChatRooms = ({ selectConvo }: SelectConvoProp) => {
const {
data: { _id },
} = auth.getUser();
const { subscribeToMore, data } = useQuery(QUERY_CONVOS);
return (
<div className="sb-convos-wrapper">
<h3 className="sb-title">Chat Rooms - {data?.convos?.length}</h3>
<Convos
convos={data?.convos}
selectConvo={selectConvo}
subscribeToConvos={() => {
subscribeToMore({
document: SUBSCRIBE_CONVOS,
variables: { _id: _id },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
return doSubscriptionAction(prev, { subscriptionData });
},
});
}}
/>
</div>
);
};
subscribeToMoreActions function
/// this function take the subscription payload and adds it to the prev query cache
const addToPrev = (prev: any, { subscriptionData }: any) => {
const newConvo = subscriptionData.data.convo;
const updatedConvos = Object.assign(
{},
{
convos: [...prev.convos, newConvo],
}
);
return updatedConvos;
};
const removeFromPrev = (prev: any, { subscriptionData }: any) => {
const deletedConvo = subscriptionData.data.convo;
const filteredConvos = prev.convos.filter(
(convo: any) => convo._id === deletedConvo._id
);
const updatedConvos = Object.assign(
{},
{
convos: [...filteredConvos],
}
);
console.log(updatedConvos);
return updatedConvos;
};
/// this function delegates which function to use based off action
export const doSubscriptionAction = (prev: any, { subscriptionData }: any) => {
if (subscriptionData.data.convo.action === "create") {
return addToPrev(prev, { subscriptionData });
}
if (subscriptionData.data.convo.action === "delete") {
return removeFromPrev(prev, { subscriptionData });
}
};
convos component
const Convos = ({ convos, selectConvo, subscribeToConvos }: ConvosProp) => {
useEffect(() => {
subscribeToConvos();
}, [subscribeToConvos]);
const [deleteConvo] = useMutation(DELETE_CONVO, {
refetchQueries: [{ query: QUERY_CONVOS }, "Convos"],
});
const handleDelete = async (_id: string) => {
await deleteConvo({ variables: { _id } });
};
return (
<div className="sb-chat-container">
{convos?.map((convo: ConvoType, i: number) => {
return (
<div className="sb-chat-item-container" key={`chatRoom ${i}`}>
<div
className="sb-chat-content-container"
onClick={() => selectConvo(convo)}
>
<div className={"sb-chat-content-container-top"}>
<h5>{convo.roomName}</h5>
</div>
<LastMessage convo={convo} />
</div>
<button onClick={() => handleDelete(convo._id)}>X</button>
</div>
);
})}
</div>
);
};
TLDR: doSubscriptionAction fires multiple times adding multiple instances of created room to other user.

ReactJS: How do I pass multiple arrays as a prop to ChildComponent?

How to map through two arrays and send data as props to other component? I want to send data from price and gamedet as a prop to Wishlist Component.As I am new to react, suggest me if this code is not good or unclean too, thanks in advance.
This is the dumb way I send whole array to Wishlist component :
price.map((game1) => {
let temp = {
steamAppID: game1.steamAppID,
storeID: game1.storeID,
normalPrice: game1.normalPrice,
salePrice: game1.salePrice,
};
console.log("temp sale", temp.salePrice);
return tempArr1.push(temp) && tempArr2.push(temp.steamAppID));
})
This is all of WishListData component:
import React, { useEffect, useState } from "react";
import Wishlist from "./Wishlist";
import "./Wishlist.css";
import "animate.css";
import axios from "axios";
const WishlistData = () => {
const [gamedet, setGameDet] = useState([]);
const [loaded, setLoaded] = useState(false);
const [stores, setStores] = useState([]);
const [price, setPrice] = useState([]);
const [wishlist, setWishlist] = useState([]);
useEffect(() => {
setWishlist(
localStorage.getItem("Wishlist")
? JSON.parse(localStorage.getItem("Wishlist"))
: []
);
}, []);
const RemoveFromWishlist = (id) => {
let newList = wishlist.filter((game) => game.gameID !== id);
setWishlist(newList)
localStorage.setItem("Wishlist", JSON.stringify(newList));
console.log("id", [wishlist.pop(id)]);
console.log("newlist", wishlist);
setGameDet([])
};
const DET_URL = `https://api.rawg.io/api/games`;
useEffect(() => {
let isCancelled = false;
const RAWGdet = () => {
wishlist &&
wishlist.map(async (game, index) => {
const res = await axios({
url: `https://cors-anywhere.herokuapp.com/${DET_URL}/${game.gameID}?key=${process.env.REACT_APP_RAWG_KEY}`,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
method: "GET",
});
if (!isCancelled) {
setGameDet((gamedet) => gamedet.concat(res.data));
}
setLoaded(true);
});
};
RAWGdet();
return () => {
isCancelled = true;
};
}, [DET_URL, wishlist]);
useEffect(() => {
let isCancelled = false;
const CSPrice = () => {
wishlist &&
wishlist.map(async (game, index) => {
const res = await axios({
url: `https://cors-anywhere.herokuapp.com/${DET_URL}/${game.slug}/stores?key=${process.env.REACT_APP_RAWG_KEY}`,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
method: "GET",
});
if (!isCancelled) {
setStores((stores) => stores.concat(res.data));
}
setLoaded(true);
});
};
CSPrice();
return () => {
isCancelled = true;
};
}, [DET_URL, wishlist]);
let stm = [];
stores
.map((steam) => {
return steam.results;
})
.filter((item) => {
return item.map((id) => {
return id.store_id === 1 ? stm.push(id.url) : <>{null}</>;
});
});
let idmain = [];
stm.map((steamid) => {
return steamid.split("/").map((item) => {
return idmain.push(item);
});
});
useEffect(() => {
let isCancelled = false
wishlist.length !==0 &&
wishlist.map((game, index) => {
return (
<div key={index}>
{axios
.get(
`https://www.cheapshark.com/api/1.0/deals?storeID=1&steamAppID=${game.steamID}`
)
.then((res) => {
if (!isCancelled){
setPrice((price) => price.concat(res.data));
setLoaded(true)
}
})
.catch((err) => {
console.log("ERR", err);
})}
</div>
);
});
return () => {
isCancelled = true
}
}, [wishlist]);
let tempArr1 = [];
let tempArr2 = [];
if (loaded ) {
return (
<div className="animate__animated animate__slideInDown">
<div className="wishlist_header">
<h3>Your Wishlist</h3>
</div>
{wishlist.length !== 0 ? (
price.map((game1) => {
let temp = {
steamAppID: game1.steamAppID,
storeID: game1.storeID,
normalPrice: game1.normalPrice,
salePrice: game1.salePrice,
};
console.log("temp sale", temp.salePrice);
return tempArr1.push(temp) && tempArr2.push(temp.steamAppID));
}) &&
gamedet.map((game, index) => {
// console.log("mad2", game.name);
return (
<div id="wishlist_ctn" key={index}>
<Wishlist
// key={index}
title={game.name}
steamRatingCount={game.id}
// steamRatingPercent={game[0].steamRatingPercent}
// savings={game[0].savings}
// normalPrice={}
// salePrice={salePrice}
steamAppID={tempArr2}
data={tempArr1}
image={game.background_image}
rem={() => RemoveFromWishlist(game.id)}
/>
</div>
);
})
) : (
<div className="wishlist_header">
<h3>Add Games!!</h3>
</div>
)}
</div>
);
} else {
return (
<div className="hmm">
<div className="wishlist_header">
<h3>Your Wishlist</h3>
</div>
<div className="wishlist_header">
<h3>Loading Games</h3>
</div>
);
</div>
);
}
};
export default WishlistData;
I don't understand why you need two extra arrays since you are mapping price to populate
tempArr1, which contain a copy of its items, and tempArr2, which contains a copy of its steamAppIDs.
I think you could just pass the price array as data, and a mapped version as steamAppID:
{wishlist.length !== 0 ?
(gamedet.map((game, index) => {
<Wishlist
// key={index}
title={game.name}
steamRatingCount={game.id}
// steamRatingPercent={game[0].steamRatingPercent}
// savings={game[0].savings}
// normalPrice={}
// salePrice={salePrice}
steamAppID={price.map(item => item.steamAppID)}
data={price}
image={game.background_image}
rem={() => RemoveFromWishlist(game.id)}
/>;
</div>
);
})
) : (
<div className="wishlist_header">
<h3>Add Games!!</h3>
</div>
)}

Error : this.state.data[index].number prints index is not defined

I need to use a DELETE request with axios to delete a book from a list. I use this.state.data[index].number to get the number of the book and pass it to the URL but the console prints an error "index is not defined". How can I solve that error?
Otherwise, when I replace index by a specific index like 1, I get the number of the book add to the URL, but my variable cible, which also needs that number to remove the book, prints null...
This is my code :
export default class ListBooks extends React.Component {
constructor(props) {
super(props);
this.state = { error: null, data: [], number: "" }
}
componentDidMount() {
Axios.get(process.env.REACT_APP_API_PATH_BOOKS)
.then(res => {
this.setState({ data: res.data });
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
/**
* Use to delete a book by the id.
*/
handleDelete = () => {
const id = this.state.data[index].number
Axios.delete(process.env.REACT_APP_API_PATH_BOOKS + id)
.then(res => {
console.log(res);
console.log(res.data);
let cible = document.getElementById("book-admin" + id);
console.log(cible);
cible.remove();
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
render() {
const { data } = this.state;
return (
<div>
<Container>
{data.map((books, index) =>
<div key={books.number}>
<ListGroup>
<ListGroup.Item disabled id={"book-admin" + data.number}>{books.number}. {books.name} {books.author}
</ListGroup.Item>
</ListGroup>
<Button variant="outline-warning" size="sm" className="btn-admin-change" id={data.number} onClick={this.props.handleUpdate}>Modifier</Button>
<Button variant="outline-danger" size="sm" className="btn-admin-change" onClick={this.handleDelete}>Supprimer</Button>
</div>
)}
</Container>
</div>
)
}
}
You're not passing the index.
Try this:
onClick={() => this.handleDelete(index)}
and
handleDelete = (index) => {
After sending the delete request if you want to remove that item from your state array you can use this:
handleDelete = (index) => {
const id = this.state.data[index].number
Axios.delete(process.env.REACT_APP_API_PATH_BOOKS + id)
.then(res => {
console.log(res);
console.log(res.data);
// let cible = document.getElementById("book-admin" + id);
// console.log(cible);
// cible.remove();
this.setState(prevState => ({ ...prevState, data: prevState.data.filter((book) => book.number !== id) }))
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
Pass the index as an argument in handleDelete function:
handleDelete = (index) => {
const id = this.state.data[index].number
Axios.delete(process.env.REACT_APP_API_PATH_BOOKS + id)
.then(res => {
console.log(res);
console.log(res.data);
let cible = document.getElementById("book-admin" + id);
console.log(cible);
cible.remove();
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
Change your second Button onClick function to:
onClick={() => this.handleDelete(index)}
The console is right, index is not defined in the scope you need it
Do this
<Button variant="outline-danger" size="sm" className="btn-admin-change" onClick={() => this.handleDelete(index)}>Supprimer</Button>
And receive the parameter in the function
handleDelete = (index) => {

React-redux rerender action type

Need to implement simple logic to switch the state of the buttons depending on the redux data. Initially, all buttons are true, but when I click on the Approve button, I send a request to the server, I get a response (false) and disabled button, then when I click on the Decline button, the Approve button should change the state to true, and Decline the button to false etc.
When I click on the decline button a second time, it returns state where approval is returned, how i can return a new state each time
Action
//Approve candidate
export const approveRequest = (token, id) => (dispatch) => {
dispatch({ type: 'FETCHING_ON' });
try {
axios.post(API.approveCandidateRequest + '/' + id,
{},
{headers: {"Authorization": 'Bearer ' + token.token }})
.then( (response) => {
if ( response.status === 200) {
dispatch(approveAction(response.data));
}
});
} catch (e) {
console.log(e);
}
};
//Decline Candidate
export const declineRequest = (token, id) => (dispatch) => {
dispatch({ type: 'FETCHING_OFF' });
try {
axios.post(API.declineCandidateRequest + '/' + id,
{},
{headers: {"Authorization": 'Bearer ' + token.token }})
.then( (response) => {
if ( response.status === 200) {
dispatch( declineAction( response.data) );
}
});
} catch (e) {
console.log(e);
}
};
Reducer
case 'SET_DECLINE_DATA_STATUS':
return {
...state,
isDecline: action.isDecline,
mergeDeclineDataToMessage: state.me.messages.push(action.isDecline.data.message)
};
case 'FETCHING_ON':
return {
...state,
isDecline: null
};
case 'SET_APPROVE_DATA_STATUS':
return {
...state,
isApprove: action.isApprove,
mergeApproveToMessage: state.me.messages.push(action.isApprove.data.message)
};
case 'FETCHING_OFF':
return {
...state,
isApprove: null
};
Logic
const ShareContacts = ( { messages, getProfileShare, declineRequest, isDecline, isApprove, approveRequest } ) => {
const shared = messages.me ? messages.me.account.shared : null;
const id = messages.me ? messages.me.account.id : null;
const [ roleId, setRoleId ] = useState('');
const [ token, setToken ] = useState('');
let declineIsDisabled = false;
let approveIsDisabled = false;
if ( isApprove && isApprove.data.is_approved !== null && isApprove.data.is_approved !== undefined
&& isApprove.data.is_approved === true ) {
approveIsDisabled = true;
declineIsDisabled = !approveIsDisabled;
console.log('isApprove', 1);
}
if (isDecline && isDecline.data.is_approved !== null && isDecline.data.is_approved !== undefined
&& isDecline.data.is_approved === false ) {
declineIsDisabled = true;
approveIsDisabled = !declineIsDisabled;
console.log('isDecline', 2)
}
isDecline = isApprove = null;
useEffect( () => {
const token = JSON.parse(localStorage.getItem("user-data"));
getProfileShare(token);
setRoleId(token.roleId);
setToken(token)
}, []);
const [open, setOpen] = useState(false);
const openModal = () => {
setOpen(true);
};
const closeModal = () => {
setOpen(false);
};
const handleApproveRequest = (e) => {
e.preventDefault();
approveRequest(token, id);
};
const handleDeclineRequest = (e) => {
e.preventDefault();
declineRequest(token, id)
};
return(
<ShareButtons>
{
roleId === 1 ? !shared ? (<Button size='tiny' color='green' onClick={openModal}>Share Contact</Button>) : null
: roleId === 2 ? shared ? (
<Flex>
<Button size='tiny' disabled={ approveIsDisabled }
onClick={handleApproveRequest} color='green'>Approve</Button>
<Button size='tiny' disabled={ declineIsDisabled }
onClick={handleDeclineRequest} color='red'>Decline</Button>
<Button size='tiny' color='grey' onClick={openModal}>Open Contacts</Button>
</Flex>
) : null :null
}
{
roleId === 1 ? (<UserDataModal open={open} closeModal={closeModal} /> )
: roleId === 2 ? ( <RecruiterDataModal id={id} open={open} closeModal={closeModal} /> ) : null
}
</ShareButtons>
);
};

Add an onClick to a submit button in React

I have this button in react
{editing && (
<Button extraClasses="m1" onClick={this.handleEditing} type="submit">
Save
</Button>
)}
But the submit doesn't work, if I delete the onClick, the submit works. How can I make both, the onClick and the submit to work?
This is the onSubmit event:
handleSubmit(e) {
e.preventDefault();
const params = this.state.params || this.props.selected.params;
const { exportTypes } = this.props;
const {
startDate: startDateMoment,
endDate: endDateMoment,
companyId,
exportTypeId,
id,
} = this.state;
const type = exportTypes.find(o => o.id === Number(exportTypeId));
let enrichedParams = [];
if (type.params.length > 0) {
enrichedParams = params.reduce((acc, { paramName, paramValue }) => {
const { id: exportParameterId } = type.params.find(p => p.name === paramName);
return [...acc, { exportParameterId, paramName, paramValue }];
}, []);
}
const startDate = startDateMoment.format();
const endDate = endDateMoment.format();
const record = { companyId, exportTypeId, startDate, endDate, id, params: enrichedParams };
const filteredQuery = Object.keys(record).reduce(
(acc, k) => (record[k] ? { ...acc, [k]: record[k] } : acc),
{},
);
if (!Object.keys(filteredQuery).length) return;
this.props.updateExport(filteredQuery);
}
You could remove the onClick event handler from your Button and invoke the handleEditing method inside your handleSubmit method instead.
Example
class App extends React.Component {
handleEditing = () => {
// ...
};
handleSubmit = (e) => {
// ...
this.handleEditing();
};
render() {
return (
<div>
{/* ... */}
{editing && (
<Button extraClasses="m1" type="submit">
Save
</Button>
)}
{/* ... */}
</div>
);
}
}

Categories