window.scrollTo() is not functioning properly in react ionic - javascript

I have a listing page in my application. When the user searches for any word the list will be displayed accordingly! To achieve this I am using two components(Let's say search and listing). When the search result is found the array will be passed to another component (i.e. listing component).
Now, the scroll-to-top button is not on the listing component. It is on the searching component. I am calling window.scrollTo(0,0) But nothing is working!
Here is the code:
const scrollToTop = () => {
window.scrollTo(0,0)
}
return (
<div className={`!overflow-y-auto container bg-satin-3 rounded-lg pt-3 pb-6 md:px-3 h-fit xl:pb-3 2xl:pb-2 lg:pb-4`} >
<ComponentCalled to display the listing />
<IonButton
onClick={() => scrollToTop()}
className="float-right"
>
Scroll to Top
</IonButton>
)

I have reuse your code and In my case it's working perfectly as shown below:
scrollToTop = () => { window.scrollTo(0,0); };
return (
<div className="css" >
<p>TOP</p>
<ComponentCalled name="! I'm working"/>
<button
onClick={() => this.scrollToTop()}
className="float-right"
>
Scroll to Top
</button>
</div>
);
https://stackblitz.com/edit/react-ts-vk1fle?file=index.tsx

Related

Open Modal in App.js from a button in another component

So I have three components in my app.
App.js
HeroSection.js
Modal.js
The button I have for opening the Modal is in HeroSection. But I need the Modal to render from App.js due to how the styling is structured. When it is opened from HeroSection, it opens in a container that doesn't allow me to position the Modal in the center of the screen (It's in a CSS grid set up). I hope I'm making sense.
My code:
const App = ({ signOut }) => {
return (
<div>
<button onClick={clickOpenAccountModal} className='add-account'>Add Account</button>
{openAddAccountModal && <Modal closeModal={setOpenAddAccountModal} />}
<div className="App">
<div className='nav-pane'>
<SideBar signOut={signOut} />
</div>
<div className='content-pane'>
<MainContent />
</div>
</div>
</div>
);
}
const HeroSection = () => {
// Add Acount Button
const [openAddAccountModal, setOpenAddAccountModal] = useState(false);
const clickOpenAccountModal = () => {
setOpenAddAccountModal(true);
}
// Add Transaction Button
const [openAddTransactionModal, setOpenAddTransactionModal] = useState(false);
const clickOpenTransactionModal = () => {
setOpenAddTransactionModal(true);
}
return (
<div className='hero-section'>
<h1 className='page-title'>Users's Dashboard</h1>
<div className='hero-buttons'>
<button onClick={clickOpenAccountModal} className='add-account'>Add Account</button>
{openAddAccountModal && <Modal closeModal={setOpenAddAccountModal} />}
<button onClick={clickOpenTransactionModal} className='add-transaction'>Add Transaction</button>
{openAddTransactionModal && <Modal closeModal={setOpenAddTransactionModal} />}
</div>
</div>
);
}
const Modal = ({ closeModal }) => {
return (
<div className='modal-background'>
<div className='modal-container'>
<div className='title-container'>
<h2>Add Account</h2>
<button className='exit-button' onClick={() => closeModal(false)}>X</button>
</div>
</div>
</div>
);
}
Main App
How the modal opens in HeroSection
How I need it to open from the button in HeroSection
I tried copying the logic for opening and closing the modal directly to App.js, but to get that to work, I had to put the button used to open the Modal in App.js as well. I need the button to stay in HeroSection and render from App.js
How can I accomplish this?
It is more valid to put the button in the HeroSection component and let the modal be opened in the App.js component. So to start it's the correct approach.
For the implementation there are 2 options:
1-
If the application won't grow to much more components, you can still have the open modal button in the HeroSection component. And that HeroSection component can take a callback function as a prop (for ex openModal) which it can calls when the button is clicked. That way when the function runs you implement the logic for opening/closing the modal in the App.js.
Check solution 1 in react codesandbox
2-
If the application can grow to many components, solution 1 will create unmaintainable code in the long run.
To address this, you can start using a general state management library like redux
When you do that, you can open/close a modal from any component you want without passing any modal specific prop to that component
In general applications, the more correct approach is solution 2. But it can be unnecessary complexity if the application only contains 3 component

React scroll with antd tabs (scrollIntoView) working for some tabs only

I'm building a multi tab chat like UI using react and antd, it looks like image below.
On the left side you can see multiple tabs showing last names using antd Tabs, on the right side I'm using antd comments to display each comment on the conversation thread
Now, the issue is that I'm trying to use useRef so it scrolls automatically to bottom when a new message is sent, and my code works, but only if I'm on the first tab or on the last one but no with the one on the middle and I'm stuck on finding out why
This is my code:
//reference and scroll function
const myRef = useRef(null);
const executeScroll = () => {myRef.current.scrollIntoView({ behavior: "smooth" })};
//useEffect associated to the source of the chat messages array
useEffect(executeScroll, [BuzonDataAgrupado]);
//And the Tab component
<Tabs tabPosition='left' onChange={handleTabChange}>
{
Object.entries(BuzonDataAgrupado).map(([tabName, mensajes]) => {
return(
<TabPane tab={tabName} key={tabName}>
<Card className='buzon-container'>
<div style={divStyle}>
{mensajes.map((mensaje) => {
return(
<Comment className='buzon-message-sent'
key={mensaje._id}
author={<a>{mensaje.nombreFamilia}</a>}
content={<p>{mensaje.Texto}</p>}
datetime={
<Tooltip title
{moment(mensaje.Fecha).format('YYYY-MM-DD HH:mm:ss')}>
<span>{moment(mensaje.Fecha).fromNow()}</span>
</Tooltip>}/>
)//return
})} //map
//This is the reference where is scrolls to at the end of message list
<div ref={myRef}></div>
</div>
<Divider />
<div className='buzon-message-editor'>
<Form.Item>
<TextArea rows={2} onChange={handleMensajeChange} value={NuevoMensaje} />
</Form.Item>
<Form.Item>
<Button htmlType="submit" loading={SendingMessage} onClick={sendMessage} type="primary">Enviar mensaje</Button>
</Form.Item>
</div>
</Card>
</TabPane>
)})
}
</Tabs>
Thoughts?
Have you debugged the myRef? Does it get always reassigned to the proper div when you change tab?
I cannot see why your code wouldn't work, but I have an idea for a workaround:
give the div an id
find the element by the id and use that to scroll
document.getElementById('your-div-id').scrollIntoView({ behavior: 'smooth' })
You could improve this if you put ref on the <Tabs> component (or its parent if it's not possible) and then you could use
tabsRef.current.getElementById('your-div-id').scrollIntoView({ behavior: 'smooth' })
Note that in javascript it might be a good idea to first test if the result of getElementById('your-div-id') is not null or undefined.

React Hook (useState, setState) is both true and false

So I have a React state variable const [pickingHotspot, setPickingHotspot] = useState(false);. I then have this button <button type="button" className="btn btn-outline-danger" onClick={() => setPickingHotspot(true)}> which just sets the state to true onClick. I have another handler
tmp.on('mousedown', (event) => {
if (pickingHotspot){
console.log(tmp.mouseEventToCoords(event));
} else {
console.log(pickingHotspot);
}
});
where tmp is a Pannellum 360 Image Viewer (its a third party library, but I don't think it matters what it is), and this is set in my useState(...,[]) which runs once on load. Lastly, I have an onClick div that just prints the value of pickingHotspot for debugging purposed. Here's the weird part:
When I load the page and click the debug div, the value is false. Cool, that works. Then I click the button (which should set it to true!) and then click the debug div again. The value is true! But when I click the Pannellum viewer, the value is false? I'm not sure how the value could possibly be both true and false, depending on where I click. Are there different versions/instances of these variables? I've tried linking everything to individual function handlers that are outside of the html components and outside of the useEffect in case there's some weird scope stuff happening, but nothing has worked so far.
I tried to show all of the code needed, but here's the full thing (I took most of the unrelated stuff out to simplify it, its a lot of code to look through.):
function TourCreator(props){
const [scenes, setScenes] = useState({});
const [media, setMedia] = useState({});
const [viewer, setViewer] = useState(null);
// Editor states
const [pickingHotspot, setPickingHotspot] = useState(false);
function handle(event){
if (pickingHotspot){
console.log(viewer.mouseEventToCoords(event));
} else {
console.log(pickingHotspot);
}
}
// Called once on load
useEffect(() => {
if (Object.keys(media).length == 0){
// Sends the request to the backend for "data"
sendGetRequest(window.$PROJECT, true, {
id: params["project_id"],
}).then((data) => {
data.images = reshapeArray(data.images, 3);
console.log(data)
setMedia(data);
let tmp = window.pannellum.viewer('panorama', tour)
setViewer(tmp);
// Print Pitch/Yaw on click
tmp.on('mousedown', (event) => handle(event));
});
}
}, [])
return (
<div className="p-3">
{/* MAIN CONTENT */}
<div className='tour-creator-root mx-auto p-3 row rounded'>
{/* MAIN BOX */}
<div className='main-box col-9 px-0 rounded'>
{/* Pannellum viewer */}
<div id='panorama' className="w-100 rounded-top">
<button type="button" className="save-button btn btn-outline-danger">
Save
</button>
</div>
{/* Toolbar */}
<div className="toolbar w-100 d-flex flex-column justify-content-center rounded-bottom p-3"
onClick={
() => {
console.log(pickingHotspot)
}
}>
<div className="d-flex flex-row justify-content-around">
<button type="button" className="btn btn-outline-danger" onClick={
() => setPickingHotspot(true)
}>
Add Hotspot
</button>
</div>
</div>
</div>
</div>
</div>
)
}
export default TourCreator;
Try passing pickingHotspot in your dependency array for useEffect.
Your event handler is attached to your element in the useEffect on componentDidMount because of the empty dependency array. This will only happen once and that old function will be used. That old function will close over the value of the previous state. You can attach your event handler again on every relevant state change by passing pickHotSpot in your dependency array.
It is also a recommended approach to keep all your relevant code inside the hook. You could have put your listener function inside your hook, and would have seen a missing dependency warning from one of your lint tools.
Also, if there is no specific reason for you to add event hanlder like this from javascript, then add inline usin JSX, like #MB__ suggested. That will be executed on every render so it should be correct. At any time only one eventhandler for the particular event will be attached.

TailwindCSS overflow-auto class not working with React Lists

I am experiencing an issue where the overflow properties of Tailwind are not working with react lists. I am wondering if this is just a simple mistake on my part or if there is a work around that I need to do.
Image to It Not Working
import CoinSummary from './CoinSummary'
const Holdings = ({ coins }) => {
return (
<div className='overflow-auto p-4'>
<h2 className='text-2xl text-center font-bold mt-4'>Holdings</h2>
{coins &&
coins.map((coin, index) => {
return <CoinSummary key={index} coin={coin} />
})}
</div>
)
}
export default Holdings
I want the list of coins to stay inside of the Holdings component and if it overflows, to have a scroll bar instead. However, as you can see in the photo, it doesn't seem to be working like I expected it to.
I was able to figure it out on my own. It was because I did not specify the height to be h-full

Text editor value disappears in React Js

I am making a simple accordion and inside each accordion, there is a text editor.
Accordion.js
<div className="wrapper">
{accordionData.map((item, index) => (
<Accordion>
<Heading>
<div
style={{ padding: "10px", cursor: "pointer" }}
className="heading"
onClick={() => toggleHandler(index)}
>
{toggleValue !== index ? `Expand` : `Shrink`}
</div>
</Heading>
<Text> {toggleValue === index && item.content && <EditorContainer />} </Text>
</Accordion>
))}
</div>
Here accordion is made up of as a component. This line {toggleValue === index && item.content && <EditorContainer />} is made to check the accordion clicked and then it loads the content and text editor accordingly.
Complete working example:
https://codesandbox.io/s/react-accordion-forked-dcqbo
Steps to reproduce the issue:
-> Open the above link
-> There will be three accordion
-> Click on any of the accordion, that will change the text from Expand to Shrink
-> Now fill some random text inside the editor then click on the text Shrink
-> Again open the same accordion by clicking Expand
-> Now already entered value is missing
I doubt it happens because every time we expand/shrink, the text_editor.js component gets called and that has the state value like,
this.state = {
editorState: EditorState.createEmpty()
};
Here instead of EditorState.createEmpty(), Should I need to give any other thing?
Requirement:
How can I store the already entered value in the text editor. Even though user clicks expand/shrink, the entered text needs to be remain there in the editor.
Any help is much appreciated.
You are correct, the entered value is missing because you are unmounting the EditorContainer component when its shrinked — that when you expand it again it creates a new editorState which is empty.
2 Possible solutions I could think of.
Move editorState and onEditorStateChange to the Parent component and pass that to EditorContainer. This way, when we unmount the EditorContainer we won't lose the previous editorState because it's on the Parent.
We wrap our EditorContainer inside a div and we'll apply a display style when we toggle between shrink/expand. This way, we are only hiding the EditorContainer not unmounting so its states will retain.
I would choose to implement the 2nd solution because we only have to make changes to our Accordion.js file. In either ways, I would create a new component that would handle the current item. I call it NormalAccordionItem.
const NormalAccordionItem = ({ data }) => {
const [show, setShow] = useState(false);
function toggle() {
setShow((prev) => !prev);
}
return (
<Accordion>
<Heading>
<div
style={{ padding: "10px", cursor: "pointer" }}
className="heading"
onClick={toggle}
>
{show ? "Shrink" : "Expand"}
</div>
</Heading>
<Text>
<div style={{ display: show ? "block" : "none" }}> // notice this
<EditorContainer />
</div>
</Text>
</Accordion>
);
};
Then on our NormalAccordion we'll use NormalAccordionItem.
const NormalAccordion = () => {
return (
<div className="wrapper">
{accordionData.map((data) => (
<NormalAccordionItem data={data} key={data.id} />
))}
</div>
);
};
That's it, check the demo below.
Edit Updated demo to expand NormalAccordionItem one at a time.

Categories