ReactJs: onClick is preemtively called - reactjs

I have 2 onClick functions
function VisitGallery(name) {
const history = useHistory();
console.log("visitgallery", name)
history.push("/gallery")
}
function App() {
const accesstoken = "******************"
const [viewport, setviewport] = React.useState({
latitude: ******
longitude: *******
width: "100vw",
height: "100vh",
zoom: 11
})
const [details, setdetails] = React.useState([
])
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore()
const data = await db.collection("data").get()
setdetails(data.docs.map(doc => doc.data()))
}
fetchData();
}, [])
const [selectedpark, useselectedpark] = React.useState(null);
React.useEffect(() => {
const listener = e => {
if (e.key === "Escape") {
useselectedpark(null);
}
};
window.addEventListener("keydown", listener)
return () => {
window.removeEventListener("keydown", listener)
}
}, [])
return (
<div className="App">
<ReactMapGl {...viewport}
mapboxApiAccessToken={accesstoken}
mapStyle="mapbox://**************"
onViewportChange={viewport => {
setviewport(viewport)
}}>
{details.map((details) =>
<Marker key={details.name} latitude={details.lat} longitude={details.long}>
<button class="marker-btn" onClick={(e) => {
e.preventDefault();
useselectedpark(details);
}}>
<img src={icon} alt="icon" className="navbar-brand" />
</button>
</Marker>
)}
{selectedpark ?
(<Popup
latitude={selectedpark.lat}
longitude={selectedpark.long}
onClose={() => {
useselectedpark(null);
}}
>
<div>
<Card style={{ width: '18rem' }}>
<Card.Body>
<Card.Title>{selectedpark.name}</Card.Title>
<Card.Text>
{selectedpark.postalcode}
</Card.Text>
<Button variant="primary" onClick = VisitGallery() >Visit Gallery</Button>
</Card.Body>
</Card>
</div>
</Popup>)
: null}
{
console.log("in render", details)
}
</ReactMapGl>
</div>
);
}
export default App;
The outer onClick is assigned when the marker is first created, and when it is clicked the useselectedpark function is called, details is then assigned to selectedpark.
The inner onClick is assigned to the function VisitGallery(). When the inner onClick is triggered, i want to navigate to another page, hence the history.push().
Ideally, what i want for it to happen is, when the outer onClick is triggered, the cardview shows, and i have an option to visit the next page, which can be triggered by an onClick within the card. However, what is happening right now is both the onClicks are triggered when i click on the thumbnail. How do i fix it such that it is how i want it to be ideally?
ps: do let me know if my explanation is confusing and i will edit it accordingly

Try adding your second onClick into a callback function?
<Button variant="primary" onClick='()=>{ VisitGallery() }' >Visit Gallery</Button>
So that it doesn't automatically invoke the function until the click is triggered.

Related

useEffect doesn't re-render on state change or infinite looping issue

I have a component which contains a form and a list. When user adds an item to the list though the form, the item should display immediately in the list. I try to use useEffect to fetch data, useEffect without dependency causes an infinite request loop. I added empty array as dependency to prevent looping but in this case new item which is added doesn't display in the list until refreshing the page. How can I solve this issue? (I use antd and antd-form-builder to create the component)
here is my code:
function FieldSetting() {
const [form] = Form.useForm()
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [])
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta} />
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting
Just add a state that will refretch (trigger useEffect) after you have submitted the form. Be aware that it will refetch all the data from the API. This might bring scalability issues when the data grows.
function FieldSetting() {
const [form] = Form.useForm()
const [refetch, setRefetch] = useState(false) // <----- add this state
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
setRefetch(!refetch) // <----- set the refetch to change the state
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [refetch]) // <----- add the refetch here to trigger the effect
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta}
/>
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting```
Whenever you manipulate your array just add a dummy state and change it
add this state
const [extra, setExtra] = useState(0)
when you change the state of your array like add or remove just add this line below
setExtra(extra+1)
what happens is that adding or removing data in an array don't count as a state change in react as per my understanding it need to be something different like true to false or in this case 0 to 1

window.scrollBy() function not working when component mount

I want to store previous offset of infinite list.my way is storing offset when user scrolling into sessionStorage, then use it whenever user back to this component.But when i navigate to another component then back to here, window.scrollBy() not working.Am i missing something or wrong somewhere of my code?
all my code
const Home = () => {
...
const navigateToChat = useNavigate();
const handleNavigateToChat = () => {
navigateToChat("/chat");
};
//Check if user scrolled then get this value to for window.scrollBy()
useEffect(()=>{
const handleGetOffset = () => {
const scrolledIndex = sessionStorage.getItem("scrolledIndex")
scrolledIndex && window.scrollBy(0,scrolledIndex)
console.log(scrolledIndex)
};
handleGetOffset ()
},[])
//Get yOffset then storing into sessionStorage
useEffect(() => {
const handleScroll = () => {
sessionStorage.setItem("scrolledIndex",window.scrollY)
};
window.addEventListener("scroll", handleScroll);
return () =>{
window.removeEventListener("scroll", handleScroll)
}
},[]);
const handleFindingUser = async () => {
//Get data from server
...
};
...
return (
<InfiniteScroll
dataLength={users.length} //This is important field to render the next data
next={handleFindingUser}
hasMore={hasMore}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: "center" }}>
<b>Yay! You have seen it all</b>
</p>
}
>
{users.map((user) => {
return (
<div
key={user.userId}
id={user.nickName}
className="d-flex"
style={{ margin: 12 }}
>
<p>{user.nickName}</p>
<Button
onClick={handleNavigateToChat}
variant="info"
style={{ marginLeft: 20 }}
>
Chat Now
</Button>
</div>
);
})}
</InfiniteScroll>
);
};
export default Home;
have a nice day, everyone!

Passing Property To Another Component - React

I need to pass "notecards" (an array) down from "Notecard.js" to "LoadQuestions.js". Console log shows that it is passing, but when I use {notecards} within the "return" it errors as "undefined". Could you please take a look?
Notecard.js (without the imports):
const useStyles = makeStyles((theme) => ({
root: {
maxWidth: 345,
},
media: {
height: 0,
paddingTop: '56.25%', // 16:9
},
}));
export default function Notecard( {notecards} ) {
const classes = useStyles();
const next = () => {
console.log('Next Button Clicked')
};
const previous = () => {
console.log('Back Button Clicked')
};
const hint = () => {
console.log('Hint Button Clicked')
};
console.log({notecards});
return (
<Card className={classes.root}>
<div id="cardBody">
<CardHeader
title="Kate Trivia"
// subheader="Hint: In the 20th century"
/>
<CardContent>
<LoadQuestions notecards={notecards}/>
</CardContent>
</div>
</Card>
);
}
LoadQuestions.js (without imports)
const {useState} = React;
export default function LoadQuestions( {notecards} ) {
const [currentIndex, setCounter] = useState(0);
console.log({notecards});
return (
<div>
<Toggle
props={notecards}
render={({ on, toggle }) => (
<div onClick={toggle}>
{on ?
<h1>{props.notecards} hi</h1> :
<h1>{this.props[currentIndex].backSide}</h1>
}
</div>
)}
/>
<button onClick={() => {
console.log({notecards})
if (currentIndex < (this.props.length-1)) {
setCounter(currentIndex + 1);
} else {
alert('no more cards')
}
}}>Next Card
</button>
<button onClick={() => {
if (currentIndex > 0 ) {
setCounter(currentIndex -1);
} else {
alert('no previous cards')
}
}}>Previous Card
</button>
</div>
);
}
Thanks in advance!
That's all the details I have for you, but stack overflow really wants me to add more before it will submit. Sorry!
You should check if props exists, first time it renders the component it has no props so it shows undefined.
First i must say you destructured notecards out, so no need to use props.
If you want to use props you should change
({notecards}) to (props)
and if not you can directly use notecards since it is destructured
I suggest you two ways
adding question mark to check if exists
<h1>{props?.notecards} hi</h1>//in the case you want to use props
or
add the props in a if statement
<h1>{props.notecards?props.notecards:''} hi</h1> // if notecards is destructured remove the "props."

How to call a function with an argument after the submitting button in the popup window was clicked?

In React I created a component that holds a local state of popup. With a little help from onClick handler I change the local state to make the popup show up. The Popup component in turn contains confirm button. I would like to call a function deleteItem ONLY after the confirm button is clicked. But I don't get how to do it. In the code below the item gets deleted right after the popup shows up but it has to be deleted only if I press the button in the Popup component. If I understand correctely, the state of the components changes when the popup shows up and I have to get it khow to the MainComponent and only in this case the function deleteItem will be called.
import {deleteItem} from './item-reducer';
const MainComponent = ({items}) => {
const [visiblePopup, setVisiblePopup] = useState(false);
return(
{items.map(item => <li key={item.id}></li>
<img onClick={() => {
setVisiblePopup(true);
deleteList(item.id) // I have to call this function after the button
in the Popup component is pressed
}}
/>
)}
<Popup setVisiblePopup={setVisiblePopup}
)
}
Popup.jsx
<div onClick={() => setVisiblePopup(false)} />
Confirm
</div>
What I have to accomplish 1) I click img and popup shows up 2) I press
'Confirm' 3) function deleteItem is invoked 4)popup dissapeares.
If I understand you correctly this is what your looking for ?
const MainComponent = ({items}) => {
const [modalState, setModalState] = useState({
display: false,
deleteItemId: undefined
});
const modalCallback = useCallback((deleteItemId)=>{
deleteItem(deleteItemId)
setModalState({ display: false, deleteItemId: undefined })
},[])
return(
<Fragment>
{
items.map(item => (
<Fragment>
<li key={item.id}></li>
<img onClick={() => setModalState({ display: true, deleteItemId: item.id })} />
</Fragment>
))
}
<PopupModal
visible={modalState.display}
deleteItemId={modalState.deleteItemId}
callback={modalCallback}
/>
</Fragment>
)
}
const PopupModal = ({ visible, deleteItemId, callback }) => {
return (visible ? <div onClick={ () => callback(deleteItemId)}>Confirm</div> : null)
}
--- OR ----
const MainComponent = ({items}) => {
const [modalState, setModalState] = useState({
display: false,
deleteItemId: undefined
});
return(
<Fragment>
{
items.map(item => (
<Fragment>
<li key={item.id}></li>
<img onClick={() => setModalState({ display: true, deleteItemId: item.id })} />
</Fragment>
))
}
modalState.display ? <div onClick={() => [deleteList(modalState.deleteItemId), setModalState({display: false, deleteItemId: undefined}) ]}>Confirm</div> : null
</Fragment>
)
}

ReactJS How to call a function that opens a simple modal?

My problem might be more related to js syntax, but the thing is, in material-ui we receive the js code for the Modal function, so how, from another file, does one calls the modal to be opened on a button click (in this case it onRowClicked in a table)
The Modal.js file with relevant code is:
export default function TransitionsModal() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
The MainTable.js code with the relevant code is:
const openPopup = rowData => {
console.log(rowData.EBELN);
TransitionsModal().setOpen(true); <---- Trying to call the Modal and open it
}
const MainTable = props => {
return (
<div className="col-md-12">
<div className="feed-toggle">
<ul className="nav nav-pills outline-active">
</ul>
</div>
<MaterialTable
columns = {columnsSetup}
options = {{ NOT_RELATED }}
onRowClick={(event, rowData) => openPopup(rowData)} -> calls Open Popup
data={query => NOT_RELATED
});
})
})
}
/>
</div>
);
};
I think that you can try to control the TransitionModal from your MainList component :
const MainTable = props => {
const [showModal , setModal] = useState;
const handleModal = status => {
setModal(status);
}
return (
<div className="col-md-12">
<TransitionsModal
showModal={showModal}
handleModal={ handleModal }
/>
<div className="feed-toggle">
<ul className="nav nav-pills outline-active">
</ul>
</div>
<MaterialTable
columns = {columnsSetup}
options = {{ NOT_RELATED }}
onRowClick={(event, rowData) => openPopup(rowData)} -> calls Open Popup
data={query => NOT_RELATED
});
})
})
}
/>
</div>
);
};
And in your TransitionModal component you just have to pass the prop to control if it will show or not 'showModal' and the method that will allow it to close it handleModal, check that ive made one method that can handle open and close, just by passing the status parameter:
export default function TransitionsModal( { showModal, handleModal } ) {
const classes = useStyles();
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={ showModal }
onClose={ () => { handleModal(false) } }
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>

Resources