React / Partially sticky footer - javascript

I am working on a nextjs project with Material UI. I am to create a page with an app bar, essentially unlimited contents in the middle, and a footer.
I am trying to create a partially sticky footer, where the Top part is just a small orange bar, which is already implemented using css
export default function Footer() {
return ( <div style={{ position: "fixed", width: "100%", backgroundColor: Colors.orange6, bottom: "0", left: "0", height: 16, }} /> );
}
I need to create a bottom part of the footer, which is partially sticky. It will only be visible when scrolled to the bottom.
What would be an elegant way to implement this?

try checking if you are at the bottom of the page and conditionally hide and show your footer.
const App = () => {
const [isBottom, setIsBottom] = React.useState(false);
const handleScroll = () => {
const bottom = Math.ceil(window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight
if (bottom) {
setIsBottom(true)
} else {
setIsBottom(false)
}
};
React.useEffect(() => {
window.addEventListener('scroll', handleScroll, {
passive: true
});
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div>content.....</div>
<footer className={isBottom ? "showFooter" : "hideFooter"}>M</footer>
)
};

Related

dispatch to redux when screen size is for I.e. large

I have a confusing issue where I want to update a portion of the state of the header when it gets to screen size large. I'm currently using tailwind so I have the following code:
Where when in mobile view, if the template is not hidden, and the screen size gets to large-- the state should automatically get updated with handleHideTemplates...
That's what I'm attempting to do. I understand with this the handle is fired immediately which I don't want...
in mobile view, if the person hits showTemplates, it hides templates immediately.
const handleHideTemplates = () => {
dispatch(setShowTemplates({ hidden: 'hidden' }));
dispatch(setTemplateMargin({ margin: '' }));
};
console.log('hidden', hidden);
return (
<header
className={`text-gray-500 text-semibold text-s fill-accent1
top-0 z-30 sticky lg:grid ${
'grid' ? handleHideTemplates() : null
} bg-templateBg px-8 ${margin}`}
>
figured it out... instead of conditional inside class..
useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 1024) {
handleHideTemplates();
}
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);

Adding a div to the bottom of a list view pushes all view upward out of viewport

I am implementing a chat view in React,and the desired behavior is that whenever new data is pushed into the dataSource, the chat view (an infinite list) scrolls to the bottom, there are many implementations, some found here: How to scroll to bottom in react?. However when I try to implement it, I am getting this strange behavior where all the views in the window are "pushed upward" out of sight by some 300px, as if to accomadate this new <div> that is at the bottom of the list view. My implementation below:
import React, {useEffect, useRef} from "react";
import { createStyles, makeStyles, Theme } from "#material-ui/core/styles";
import InfiniteScroll from 'react-infinite-scroll-component';
const row_1 = 2.5;
const row_chat = 4
const useStyles = makeStyles((theme: Theme) => createStyles({
container: {
width: '40vw',
height: `calc(100vh - 240px)`,
position: 'relative',
padding: theme.spacing(3),
},
}));
const chat_container_style = {
width: '40vw',
height: `calc(100vh - 240px - ${row_chat}vh - ${row_1}vh)`,
}
function ChatView(props) {
const classes = useStyles();
const { _dataSource } = props;
// scroll to bottom
const messagesEndRef = useRef(null)
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
}
useEffect(() => {
scrollToBottom()
}, [_dataSource]);
return (
<div className={classes.container}>
{/* chat window */}
<InfiniteScroll
dataLength={_dataSource.length}
next={() => { return }}
hasMore={true}
loader={<></>}
style={chat_container_style}
>
{_dataSource.map((item, index) => {
return (
<div {...props} key={index} item={item}>
{`item: ${index}`}
</div>
)
})}
{/* putting an item here push all divs upward */}
<div ref={messagesEndRef} />
</InfiniteScroll>
</div>
)
}
Note the use of <InfiniteScroll/> is not the cause of the behavior, Really if I put the ref={messagesEndRef} into any view, it pushes all the views up in the viewport.
The issue has been resolved. The source of the issue is the scrollIntoView function, it's scrolling the entire page instead of just the listView, here's the correct scrollIntoView function with the correct parameters:
const scrollDivRef = createRef();
useEffect(() => {
scrollDivRef.current?.scrollIntoView({
block : 'nearest',
inline : 'start',
behavior: 'smooth',
})
}, [_dataSource.length]);
Here's how the ref is nested inside the DOM:
<InfiniteScroll
next={() => { return }}
hasMore={true}
loader={<></>}
style={chat_container_style}
dataLength={_dataSource.length}
>
{_dataSource.map((item, index) => (
<BubbleView {...props} key={index} item={item}/>
))}
<div style={refDivStyle} ref={scrollDivRef}/>
</InfiniteScroll>
This problem has nothing to do w/ how I layout the style sheet.

How to auto hide and show component in react native

In my home screen I want to auto hide my header in 2 seconds, then I will have a button to show the header when pressed. I have tried with HomeStack.Screen but could not achieve it, I have to create my custom header called HeaderHomeComponent.js and imported it on my homescreen, still I could not achieve it. Please I need help on this issue.
Here is my code:
const [showHeader, setShowHeader] = useState(true);
const onRecord = async () => {
if (isRecording) {
camera.current.stopRecording();
} else {
setTimeout(() => setIsRecording && camera.current.stopRecording(), 23*1000);
const data = await camera.current.recordAsync();
}
};
const visibility = () => {
setTimeout(() => setShowHeader(false), 2000);
}
return (
<View style={styles.container}>
<RNCamera
ref={camera}
type={cameraType}
flashMode={flashMode}
onRecordingStart={() => setIsRecording(true)}
onRecordingEnd={() => setIsRecording(false)}
style={styles.preview}
/>
<HeaderHomeComponent />
You can create a function with useeffect.
Make sure you passs show and handleClose functions from Parent. (Example given below).
const MessageBox = (props) => {
useEffect(() => {
if (props.show) {
setTimeout(() => {
props.handleClose(false);
}, 3000);
}
}, [props.show]);
return (
<div className={`messageBox ${props.show ? "show" : null}`}>
{props.message}
</div>
);
};
UseEffect will be called everytime props.show state will change. And we only want our timer to kick in when the show becomes true, so that we can hide it then.
Also, now to use this, it's simple, in any component.
const [showMessageBox, setShowMessageBox] = useState(false);
return(
<MessageBox
show={showMessageBox}
handleClose={setShowMessageBox} />
);
Also, make sure to handle css, part as well for show and hide.
Simple Example below.
.messageBox {
display: none;
}
.messageBox.show {
display: block;
}
Hope this helps, :-)
You need to do something like this as Mindaugas Nakrosis mentioned in comment
const [showHeader, setShowHeader] = useState(true);
useEffect(() => {
setTimeout(() => setShowHeader(false), 2000);
}, []);
In return where your header is present
{
showHeader && <HeaderHomeComponent/>;
}
I think the approach gonna fit "auto hide and show in 2 seconds", is using Animetad opacity, and giving fix height or/and z-index (as fit you) to the element
// HeaderHomeComponent.js
const animOpacity = useRef(new Animated.Value(1)).current // start with showing elem
//change main view to
<Animated.View
style={{ ...yourStyle... ,
opacity: animOpacity,
}}
>
and then for creating the animation somewhere
() => {
Animated.timing(animOpacity, {
toValue: +(!animOpacity), // the numeric value of not current
duration: 2000, // 2 secs
}).start();
}}
The hieraric location of the declaration of the ref should control usage as calling the effect. maybe you can create useEffect inside the header that can determine if it should be visible or not depends navigation or some other props.
hope its helpful!

How to fade in element on scroll using React Hooks/React Spring

I'm attempting to create an animation in which one element fades based upon the scroll position of another. I was able to get the scrolling element to work using React Spring, but I'm struggling to wrap my head around how to leverage state hooks without conditionals and only being able to set state at a component top level.
SandBox
const HomeView = () => {
const ref = useRef();
const [isVisible, setVisible] = useState(true);
const [{ offset }, set] = useSpring(() => ({ offset: 0 }));
const calc = (o) => {
if (o < 1004) {
return `translateY(${o * 0.08}vh)`;
} else {
// this won't work b/c im trying to useState in a Fn
setVisible(false);
return `translateY(${1012 * 0.08}vh)`;
}
};
const handleScroll = () => {
const posY = ref.current.getBoundingClientRect().top;
const offset = window.pageYOffset - posY;
set({ offset });
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
});
return (
<div ref={ref} className="home-view" style={homeViewStyles}>
<div style={topSectionStyles} className="top-content-container"></div>
<animated.div
className="well-anim"
style={{
width: "100vw",
height: "500px",
transform: offset.interpolate(calc),
zIndex: 300,
top: "340px",
position: "absolute"
}}
>
<h1>Well,</h1>
</animated.div>
{/* Trying to fade this component when above animated.div is right above it */}
<h2 style={{ paddingTop: "90px" }} fade={isVisible}>
Hello There!
</h2>
{/***************************************/}
</div>
);
};
You are almost there. I think the problem here is with the fade attribute. The setVisible function is invoked all right. I would introduce a second spring to deal with the opacity with the h2 element, based on the state of the isVisible variable.
const {opacity} = useSpring({opacity: isVisible ? 1 : 0});
<animated.h2 style={{ paddingTop: "90px", opacity }} >
Hello There!
</animated.h2>
https://codesandbox.io/s/wild-dew-tyynk?file=/src/App.js

Add and remove css class only during scroll in React?

I am working on a mockup blog for my portfoilio which has a grid of posts which are 400x400 cards with a hover effect that increases scale and adds a drop shadow (this is rendered in the dashboard route).
I have noticed however that when scrolling my page, the pointer will get hung up on the cards and stop registering scroll when the animation is enabled. I have come across this article (https://www.thecssninja.com/javascript/pointer-events-60fps) discussing the performance benefits of disabling pointer events to the body on scroll however I cannot figure out how to do this in react.
How would you add a class to the document body in React ONLY while scrolling and remove that class as soon as the scroll event had ended? Maybe with a setTimeOut in some way?? I feel like that would be a janky solution...
I included the code I am looking to implement this in, I don't know if that will help. The scroll event I have set up already is to maintain the scroll position of the page while the navbar is extended.
export default class Layout extends Component {
constructor(props) {
super(props);
this.state = {
hasMenuOpen: false,
scroll: {x: 0, y: 0},
};
this.handleScrollY = _.debounce(this.handleScrollY, 250);
this.handleWidthChange = _.debounce(this.handleWidthChange, 250);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScrollY);
window.addEventListener('resize', this.handleWidthChange);
console.log('mount');
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.hasMenuOpen) {
/* eslint-disable */
let {x, y} = prevState.scroll;
/* eslint-enable */
// correct scroll y position back to 0 for positions <= 100
window.scrollTo(x, (y <= 100 ? y = 0 : y));
}
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScrollY);
window.removEventListener('resize', this.handleWidthChange);
}
// if menu is open an y = 0 (i.e position: fixed was added),
// scroll = previous states scroll
// else if you scroll and the menu isn't open, scroll = windows new scroll pos
handleScrollY = () => {
const y = window.scrollY;
const x = window.scrollX;
this.setState((prevState) => {
if (this.state.hasMenuOpen && y === 0) {
return {scroll: Object.assign({}, prevState.scroll)};
}
return {scroll: Object.assign({}, prevState.scroll, {x}, {y})};
});
}
handleWidthChange = () => {
console.log(window.innerWidth);
if (this.state.hasMenuOpen) {
return this.handleBurgerClick();
}
return null;
}
handleBurgerClick = () => {
this.setState((prevState) => ({
hasMenuOpen: !prevState.hasMenuOpen
}));
}
handleLinkClick = () => {
this.setState((prevState) => ({
hasMenuOpen: false
}));
}
render() {
const scrollTop = {
top: `-${this.state.scroll.y}px`,
};
console.log(this.state.scroll);
return (
<React.Fragment>
<Navbar onClick={this.handleBurgerClick} hasMenuOpen={this.state.hasMenuOpen} onLinkClick={this.handleLinkClick} />
<div className={this.state.hasMenuOpen ? styles.scroll_lock : ''} style={this.state.hasMenuOpen ? scrollTop : {}}>
<main className={styles.page_margin} >
<div className={this.state.hasMenuOpen ? styles.margin_extended : styles.margin_top}>
<Route path='/' exact component={Dashboard} />
<Route path='/new-post' component={NewPost} />
</div>
</main>
</div>
</React.Fragment>
);
}
}
For example I have tried setting document.body.style.pointerEvents = 'auto' in componentDidMount() and disabling it in the handleScrollY() however this obviously doesn't work as pointer events are never restored once the scroll event occurs. I have also tried setting it in componentDidUpdate() but that doesn't seem to work either as no component is being updated when the scroll event isn't happening.
One way of toggling a css class is:
componentWillUnmount() {
document.removeEventListener('scroll');
}
componentDidMount() {
document.addEventListener('scroll', (event) => {
const that = this;
if (!that.state.isScrolling) {
that.setState({ isScrolling: true });
setTimeout(() => {
that.setState({ isScrolling: false });
}, 300);
}
});
}
And then use the state or props to toggle the className
in your JSX something like
return (
<div className={`class1 class2 class99 ${this.state.isScrolling ? 'scrolling' : 'not-scrolling'}`} >Content</div>
)

Categories