How to reveal a React component on scroll - javascript

I've created a React component for a fixed nav that I would like to remain hidden, until I scroll past a certain point on the page, then slides into view. Medium has a header similar to what I'm describing.
This is a relatively trivial task in jQuery, with scrollmagic or waypoints but is there an idiomatic way of accomplishing this with React and vanilla JS?

React Way with vanilla JS jsfiddle;
don't forget to remove EventListener. In this example component will render if only it is neccessary
class TopBar extends React.Component {
state = { isHide: false };
hideBar = () => {
const { isHide } = this.state
window.scrollY > this.prev ?
!isHide && this.setState({ isHide: true })
:
isHide && this.setState({ isHide: false });
this.prev = window.scrollY;
}
componentDidMount(){
window.addEventListener('scroll', this.hideBar);
}
componentWillUnmount(){
window.removeEventListener('scroll', this.hideBar);
}
render(){
const classHide = this.state.isHide ? 'hide' : '';
return <div className={`topbar ${classHide}`}>topbar</div>;
}
}

You could use a component such as react-headroom to do the heavy lifting for you. Or, you can still use waypoints in React, setting it up in the componentDidMount lifecycle method and removing it using componentWillUnmount.

In the componentDidMount lifecycle hook, do the same thing as in the jQuery link you have given:
class Navbar extends React.component {
let delta = 5;
render() {
return (
<div ref=header></div>
);
}
componentDidMount() {
$(window).scroll(function(event){
var st = $(this).scrollTop();
if(Math.abs(this.state.lastScrollTop - st) <= delta)
return;
if (st > lastScrollTop){
// downscroll code
// $(this.refs.header).css('visibility','hidden').hover ()
this.setState({
navbarVisible: false
});
} else {
// upscroll code
$(this.refs.header).css('visibility','visible');
this.setState({
navbarVisible: true
});
}
lastScrollTop = st;
}.bind(this));
}
}

I created a react component for this same exact need as I could not find any other implementations that matched what I needed. Even react-headroom did not give you something that would just scroll in after reaching a certain point on the page.
The gist is here: https://gist.github.com/brthornbury/27531e4616b68131e512fc622a61baba
I don't see any reason to copy the component code here. The code is largely based off of the react-headroom code but does less and is therefore simpler.
The component is the first piece of code, you could simply copy/paste then import it. After importing your code with the navbar would look something like this:
class MyScrollInNavBar extends Component {
render() {
return (
<ScrollInNav scrollInHeight={150}>
<MyNavBar />
</ScrollInNav>
);
}
}

Related

Can't access to a 'this' value in a function outside of constructor

I'm trying to learn how to make an app like reactjs but not really using it. I'm following a tutorial but I have some challenges. I have a function called 'update' which fires when there is a change in the state. I have a 'menu' object that I import as a child. The thing is, I can't seem to access to this child object in the 'update' function. Please have a look at the following:
import onChange from 'on-change';
import Menu from './menu';
class App {
constructor(){
const state = {
showMenu: false
}
this.state = onChange(state, this.update);
this.el = document.createElement('div');
this.el.className = 'todo';
// create an instance of the Menu
this.menu = new Menu(this.state);
// create a button to show or hide the menu
this.toggle = document.createElement('button');
this.toggle.innerText = 'show or hide the menu';
this.el.appendChild(this.menu.el);
this.el.appendChild(this.toggle);
// change the showMenu property of our state object when clicked
this.toggle.addEventListener('click', () => { this.state.showMenu = !this.state.showMenu; })
}
update(path, current, previous) {
if(path === 'showMenu') {
> // show or hide menu depending on state
> console.log (app.menu); // undefined
> this.menu[current ? 'show' : 'hide'](); // not working cause 'this.menu' is undefined
}
}
}
const app = new App();
> console.log (app.menu); // here it console logs correctly the object
document.body.appendChild(app.el);
Can someone help me to figure out what is going on here? thank you!
In the update() method in your App class, you have to use this.menu instead of app.menu.
It should look like this:
update(path, current, previous) {
if(path === 'showMenu') {
console.log (this.menu);
this.menu[current ? 'show' : 'hide']();
}
}
You can't use app within the App class because app is not defined there. You have to use the this keyword to access the members in the class itself.
Hope this helps.

Vue acting weird with condition relating to window width

So I am just trying to get my basic Vue navbar functions working such as changing class on scroll (which works fine) and changing class on resize, which I'm having a bit more trouble with.
Here is the content of my <template> tags:
<nav class="navbar is-fixed-top navbar-max">
{{windowWidth}}
</nav>
...and the relevant content of my <script> tags:
export default {
name: "Navbar",
data () {
return {
windowWidth: window.innerWidth
}
},
created () {
window.addEventListener('resize', this.onResize);
},
mounted () {
this.windowWidth = window.innerWidth
},
beforeDestroy () {
window.removeEventListener('resize', this.onResize);
},
methods: {
onResize() {
let navbar = document.querySelector(".navbar");
if (this.windowWidth > 768) {
console.log(this.windowWidth),
navbar.classList.remove("nav-mobile"),
navbar.classList.add("nav-desktop")
}
else {
console.log(this.windowWidth),
navbar.classList.remove("nav-desktop"),
navbar.classList.add("nav-mobile")
}
}
}
}
My issue is really odd - all my console.log()'s output the correct width, and so does {{windowWidth}} in the navbar, and even the adding and removing classes works! It's just that the changed classes don't seem to have any effect until windowWidth = 1024 and I have no idea why...
Any help?
Cheers :)
You never set this.windowWidth after mount.
Simply add the following:
onResize() {
this.windowWidth = window.innerWidth // add this line
...
}
I would also like to point out that what it looks like you're trying to achieve (different nav-bar-styling on mobile and desktop) is very doable without any vue-/js-magic using CSS #media-queries.
And if you still wish to do it with vue, you should do it the vue way:
Make a computed method like so:
computed: {
isMobile() {
return this.windowWidth <= 768
}
}
and then update the class directly on the nav-tag using class-binding.

ReactJS: how to hide element based on parent's props?

I have a component that is showing message box. It's called from the parent element something like that:
<AlertMessage status={this.state.status} text={this.state.text} />
I want to change it to take one more parameter hide and it would be hiding itself in 5 seconds:
<AlertMessage status={this.state.status} text={this.state.text} hide={true} />
The problem this alert message is based on props, so its render function looks like:
render(){
const statusName = this.props.status || 'info';
const alertStyle = 'alert-box ' + alertClasses[statusName] + (this.props.text ? '' : ' d-none');
return (
<div className={alertStyle}>
<span>{this.props.text}</span>
</div>
);
}
So, I see 2 ways to implement hiding after 5 seconds:
A parent element does it just setting up the text to ""
The alert component hides itself.
I don't want to involve the parent component here but I don't know how I would implement it inside alert component. Add more state like "hidden" to the alert component but how I would handle it after?
Update. I think I found the solution. I use static function getDerivedStateFromProps and the component's visibility is based on its state hide, not on the text:
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.text != nextProps.message && nextProps.message){
return {
text: nextProps.message,
hide: false
};
} else {
return {};
}
}
render(){
const statusName = this.props.status || 'info';
const alertStyle = this.state.hide ? ' d-none' : 'alert-box ' + alertClasses[statusName];
if (!this.state.hide){
setTimeout(() => this.setState({hide: true}), 5000);
}
}
There is still bug: when I show several messages in a row the total timeout will start on the first, not on the last, but it's okay for now.
It will be difficult to handle it properly without involving the parent element. If you clear the text prop on parent, the AlertMessage component will still be mounted, but will be no visible due to no content inside. The same situation if you would drop the logic inside AlertMessage - you can't "unmount" the component directly from inside it.
The disadvantage is that AlertMessage remains mounted, you may have problems with applying animations to it. Also it may take the space in your app (depends on css), so users can accidently click on it or may cause problems with clicking elements placed under it.
What I would suggest - use your hide prop. Inside that function, where you set hide to false and the alert appears - use setTimeout, so the hide prop goes true after n seconds.
const someFn = () => {
this.setState({ hide: false }); // alert appears
setTimeout(() => {
this.setState({ hide: true });
}, 5000); // alert disappears (gets unmounted)
};
Then inside your render:
{!hide && <AlertMessage status={this.state.status} text={this.state.text} />}
constructor(props) {
super(props);
this.state = {
hide: false
};
componentDidMount() {
this.timer = setTimout(() => this.setState({ hide: true }), 5000)
}
componentWillUnmount() {
this.timer && clearTimeout(this.timer);
render(){
const statusName = this.props.status || 'info';
const alertStyle = 'alert-box ' + alertClasses[statusName] + (this.props.text ? '' : ' d-none');
return (
<div className={alertStyle}>
<span>{this.state.hide ? "" : this.props.text}</span>
</div>
);
}
Just get a state variable hide. setTimeout will turn it to false after 5 seconds. Don't forget to clear it in componentWillUnmount.

How to scroll to top in antd steps. react

I am using Steps component of antd.
I want whenever a user goes to the next step or to the previous step the page should scroll to top.
I use window.scrollTo(0, 0) and window.top=0; but it does not work.
can anyone help how can I scroll to top.
previousStep = () => {
window.scrollTo(0, 0);
window.scrollTop = 0;
const { currentStep } = this.state;
this.setState({ currentStep: currentStep - 1 });
};
onstructor(props) {
super(props)
this.myRef = React.createRef() // Create a ref object
}
componentDidMount() {
this.myRef.current.scrollTo(0, 0);
}
render() {
return <div ref={this.myRef}></div>
} // attach the ref property to a dom element

How to get return value each state changed in another componentin React

In a React Component Passed a value, if Component Change state, How to return a new value from this component.
If ScrollDetecter state changed how to return value this component?
<ScrollDetector/>
export default class ScrollDetector extends React.Component {
//Most impotant function only metioned
handleScroll() {
const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight) {
this.setState({
message:'bottom reached'
});
} else {
this.setState({
message:'not at bottom'
});
}
}
}
How to a particular value to parent component return if bottom reached message state.?
You want to change a parent component on event in child component(ScrollDetector)?
If so pass the callback function, that will update parent component, as a prop.
Let's say you want to change the state of parent component based on scroll. You would have something like this.
<ScrollDetector checkIfBottomReached={this.checkIfBottomReached} />
while the implementation of function would be
isBottomReached() {
this.setState({ isBottomReached: true });
}
or simply pass it shorthanded
<ScrollDetector checkIfBottomReached={() => this.setState({ isBottomReached: true })} />
and inside ScrollDetector component you would call this function when you reach the bottom, just by calling this.props.checkIfBottomReached()
You could use and event handler / callback function and send a prop from parent to child so it notifies when bottom has been reached:
class ScrollDetector extendsReactComponent {
handleScroll() {
...
if (windowBottom >= docHeight) {
this.props.onBottomReached()
} else {
...
}
}
}
and from your parent component:
class YourParent extends React.Component {
render() {
return (
<div>
<ScrollDetector
onBottomReached={() => console.log('bottom reached!') }
/>
</div>
)
}
}

Categories

Resources