ReactJS — Creating Refs and accessing DOM elements to use fullpage.js - javascript

I am trying to migrate a code I created in JS/JQuery to ReactJS. The code uses the library fullpage.js. The aim is to change the title according to the section on focus. I am struggling to correctly capture the properties of elements in the DOM using Refs. I tried to follow the ReactJS documentation but the example in the docs made everything more confusing.
My question is, how can I store these properties and then apply it using CSS on the required DOM element? Thank you in advance.
Pseudocode
Store title width
Store title height
Apply these properties to the
parents' frame and mask
Listen to the scroll events of fullpage.js
and translate the title position accordingly
Codepen (JS/JQuery)
$(document).ready(function() {
var titleWidth = $(".title").outerWidth();
var titleHeight = $(".title").outerHeight();
$("#mask").css({ height: titleHeight + "px", width: titleWidth + "px" });
$("#frame").css("top", titleHeight);
new fullpage("#fullpage", {
sectionsColor: ["yellow", "orange", "#C0C0C0", "#ADD8E6"],
afterRender: function() {
$("#frame").transition({ top: "-=" + titleHeight, delay: 1000 });
},
onLeave: function(origin, destination, direction) {
var leavingSection = this;
//after leaving section 2
if (direction == "down") {
$("#frame").transition({ top: "-=" + titleHeight });
} else if (direction == "up") {
$("#frame").transition({ top: "+=" + titleHeight });
}
}
});
});
Codesandbox (ReactJS)
import React from "react";
import ReactDOM from "react-dom";
import "fullpage.js/vendors/scrolloverflow"; // Optional. When using scrollOverflow:true
import ReactFullpage from "#fullpage/react-fullpage";
import "./styles.css";
class MySection extends React.Component {
render() {
return (
<div className="section">
<h3>{this.props.content}</h3>
</div>
);
}
}
const anchors = ["firstPage", "secondPage", "thirdPage"];
const FullpageWrapper = () => (
<ReactFullpage
anchors={anchors}
navigation
navigationTooltips={anchors}
sectionsColor={["#282c34", "#ff5f45", "#0798ec"]}
onLeave={(origin, destination, direction) => {
console.log("onLeave event", { origin, destination, direction });
//after leaving section 2
if (origin.index === 1 && direction === "down") {
alert("Going to section 3!");
} else if (origin.index === 1 && direction === "up") {
alert("Going to section 1!");
}
}}
render={({ state, fullpageApi }) => {
console.log("render prop change", state, fullpageApi); // eslint-disable-line no-console
return (
<div>
<MySection content={"Slide down!"} />
<MySection content={"Keep going!"} />
<MySection content={"Slide up!"} />
</div>
);
}}
/>
);
class Index extends React.Component {
constructor(props) {
super(props);
this.title = React.createRef();
// this.title = null;
// this.titleRef = element => {
// this.title = element;
// };
// this.setTitle = () => {
// Focus the text input using the raw DOM API
// if (this.title) this.title.current.offsetWidth;
// };
}
componentDidMount() {
// console.log(this.setTitle())
const titleWidth = this.title.current.offsetWidth;
const titleHeight = this.title.current.offsetHeight;
console.log(titleWidth, titleHeight);
}
render() {
return (
<div class="wrapper">
<div id="mask">
<div id="frame">
<h1 ref={this.title} class="title">
One
</h1>
<h1 ref={this.title} class="title">
Two
</h1>
<h1 ref={this.title} class="title">
Three
</h1>
<h1 ref={this.title} class="title">
Four
</h1>
</div>
</div>
<div class="content">
<FullpageWrapper />
</div>
</div>
);
}
}
ReactDOM.render(<Index />, document.getElementById("react-root"));

Related

Intersection Observer Scrolling Problem On Mobile Devices

I'm having trouble with Intersection Observer in Gatsbyjs.
There is a fixed header that needs to change the color when some section enters 90% of the viewport.
I've created a component called IntersectionObserverComponent that renders a div with absolute position and height of 100vh. The idea is to include this component into a section that I want to track with Intersection Observer.
Here's my component
class IntersectionObserverComponent extends React.PureComponent {
constructor(props) {
super(props)
//
this.observer = null
this.ref = React.createRef()
}
componentDidMount() {
const { root, threshold, callback } = this.props
('IntersectionObserver' in window
? Promise.resolve()
: import('intersection-observer')
).then(() => {
this.observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
callback(entry)
})
},
{ threshold, root }
)
if (this.ref.current) {
this.observer.POLL_INTERVAL = 100
this.observer.observe(this.ref.current)
}
})
}
componentWillUnmount() {
if (this.ref.current && this.observer) {
this.observer.unobserve(this.ref.current)
}
}
render() {
return (
<div
ref={this.ref}
style={{
height: '100vh',
position: 'absolute',
top: 0,
left: 0,
right: 0,
}}
></div>
)
}
}
export default IntersectionObserverComponent
And here's the usage
import { handleHeaderStyle } from '../../utils/intersectionObserver'
const About = ({ isTrackable = false, handleChangeHeader = () => {} }) => {
// previousValues object is directly mutated in a handleHeaderStyle fn
const previousValues = { previousY: 0, previousRatio: 0 }
const colors = { prevColor: 'white', currColor: 'secondary' }
const handleIOCallback = entry => {
console.log(entry)
handleHeaderStyle(entry, colors, previousValues, handleChangeHeader)
}
return (
<section className="padding-top-m padding-bottom-tablet-portrait-96 header-behind background-gray-small-only">
{isTrackable ? <IO callback={handleIOCallback} /> : null}
<Container>
<Grid className="grid-padding-x">
<Cell small={12} large={8}>
<p className="margin-bottom-small-20 margin-bottom-tablet-portrait-32">
We are a brand and product development consultancy, that believes
in that design is essentially a problem-solving exercise that we
can apply to any sector, discipline or media.
</p>
<p className="margin-bottom-small-32">
We are founded on and by people who are driven by:
</p>
<ul className="arrow-bullets">
<li>
Crafting simple and notable components that build up your
brand’s value
</li>
<li>Discovery and research aimed at thoughtful practices</li>
<li>Attention to detail</li>
<li>Independence and creative freedom</li>
<li>Thinking that goes deeper</li>
</ul>
</Cell>
<Cell className="padding-top-m">
<Projects />
</Cell>
</Grid>
</Container>
</section>
)
}
export default About
And here's the function that handles logic for updating header
export const handleHeaderStyle = (
IOEntry,
colors,
previousValues,
callback
) => {
if (!IOEntry || !colors || !previousValues || typeof callback !== 'function')
return
//
const { prevColor, currColor } = colors
const currentY = IOEntry.boundingClientRect.y
const currentRatio = IOEntry.intersectionRatio
const isIntersecting = IOEntry.isIntersecting
// going up
if (currentY > previousValues.previousY) {
if (currentRatio < previousValues.previousRatio && !isIntersecting) {
callback(prevColor)
}
}
// going down
else if (currentY < previousValues.previousY && isIntersecting) {
if (currentRatio > previousValues.previousRatio) {
callback(currColor)
}
}
previousValues.previousY = currentY
previousValues.previousRatio = currentRatio
}
This is working really nice on all screen sizes when I test it in a desktop browser but the problem occurs when I test it on an actual mobile device.
Device: Iphone 7+, IOS 13.3.1
Browsers: Safari 12.1.1, Chrome 80
It works great if I don't release my finger while scrolling, but If I do, the callback is not fired consistently. I've also tried setting the POLL_INTERVAL to 100ms but it didn't help.
I'm suspecting that has something to do with the way iOS Safari handles the scroll event (https://github.com/martinlaxenaire/curtainsjs/issues/18)
Here's the test link on Netlify - https://nifty-einstein-c4cf08.netlify.com/
I'm trying to figure it out for days now, thanks in advance

Problem with event listeners not being added again, once React unmounts them

I am very new to react, and i decided to build a website using create-react-app for experience. I imported a Gallery component into my code which is causing me some problems. It's basically a Picture Gallery with a filtering and shuffle option which works fine whenever you completely reload the page, yet stops working once you switch between routes (with react-router) inside the webpage itself.
My guess is that the eventListeners are not being added to my buttons once react unmounts them, and I am really unsure as to how to refactor the code at hand, inorder to get it going. I have tried using Hooks but I can't make it work.
How do I refactor the
componentDidMount () {
document.addEventListener('DOMContentLoaded', () => {
window.demo = new Demo(document.getElementById('grid'));
});
}
part of the code in order to get it working without having to reload the entire page?
Homebase.js
import React, { Component } from 'react';
import './Homebase.scss';
import Shuffle from 'shufflejs';
import { Link } from 'react-router-dom';
import Demo from './Homebasescript.js';
class homebase extends Component {
componentDidMount () {
document.addEventListener('DOMContentLoaded', () => {
window.demo = new Demo(document.getElementById('grid'));
});
}
render() {
return (
<section>
<div className="homebase-page">
<div className="container-about">
<div className="row-about">
<div className="homebase-title-container">
<h1 className="homebase-about-title">Townhall <span style={{color: 'rgb(21, 115, 209)'}}>12</span> Base Designs</h1>
<div className="switch-container-th12">
<button className="switch-buttons-th12 switch-to-th9-12">
<div className="switch-buttons-text-12">coming soon</div>
</button>
<button to="/Townhall-10" className="switch-buttons-th12 switch-to-th10-12">
<div className="switch-buttons-text-12">Townhall 10</div>
</button>
<button to="/Townhall-11" className="switch-buttons-th12 switch-to-th11-12">
<div className="switch-buttons-text-12">Townhall 11</div>
</button>
<button to="/Townhall-12" className="switch-buttons-th12 switch-to-th12-12">
<div className="switch-buttons-text-12">Townhall 12</div>
</button>
</div>
</div>
</div>
</div>
<div className="container-about">
<div className="row-about">
<div className="col-4#sm col-3#md">
<div className="filters-group filters-group-left">
<label htmlFor="filters-search-input" className="filter-label filter-color">Search</label>
<input className="textfield filter__search js-shuffle-search" type="search" id="filters-search-input" />
</div>
</div>
</div>
<div className="row-about">
<div className="col-12#sm filters-group-wrap">
<div className="filters-group filters-group-right">
<p className="filter-label filter-color">Filter</p>
<div className="btn-group filter-options">
<button className="btn btn--primary" data-group="war">War</button>
<button className="btn btn--primary" data-group="trophy">Trophy</button>
<button className="btn btn--primary" data-group="farm">Farm</button>
<button className="btn btn--primary" data-group="fun">Fun</button>
<button className="btn btn--primary" data-group="contributors">Contributors</button>
</div>
</div>
<fieldset className="filters-group">
<legend className="filter-label filter-color">Sort</legend>
<div className="btn-group sort-options">
<label className="btn active">
<input type="radio" name="sort-value" value="dom" defaultChecked /> Default </label>
<label className="btn">
<input type="radio" name="sort-value" value="title" /> Title </label>
<label className="btn">
<input type="radio" name="sort-value" value="date-created" /> Date Created </label>
</div>
</fieldset>
</div>
</div>
</div>
</div>
</section>
);
};
};
export default homebase;
and the other file is
Homebasescript.js
import Shuffle from 'shufflejs';
class Demo {
constructor(element) {
this.element = element;
this.shuffle = new Shuffle(element, {
itemSelector: '.picture-item',
sizer: element.querySelector('.my-sizer-element'),
});
// Log events.
this.addShuffleEventListeners();
this._activeFilters = [];
this.addFilterButtons();
this.addSorting();
this.addSearchFilter();
}
/**
* Shuffle uses the CustomEvent constructor to dispatch events. You can listen
* for them like you normally would (with jQuery for example).
*/
addShuffleEventListeners() {
this.shuffle.on(Shuffle.EventType.LAYOUT, (data) => {
console.log('layout. data:', data);
});
this.shuffle.on(Shuffle.EventType.REMOVED, (data) => {
console.log('removed. data:', data);
});
}
addFilterButtons() {
const options = document.querySelector('.filter-options');
if (!options) {
return;
}
const filterButtons = Array.from(options.children);
const onClick = this._handleFilterClick.bind(this);
filterButtons.forEach((button) => {
button.addEventListener('click', onClick, false);
});
}
_handleFilterClick(evt) {
const btn = evt.currentTarget;
const isActive = btn.classList.contains('active');
const btnGroup = btn.getAttribute('data-group');
this._removeActiveClassFromChildren(btn.parentNode);
let filterGroup;
if (isActive) {
btn.classList.remove('active');
filterGroup = Shuffle.ALL_ITEMS;
} else {
btn.classList.add('active');
filterGroup = btnGroup;
}
this.shuffle.filter(filterGroup);
}
_removeActiveClassFromChildren(parent) {
const { children } = parent;
for (let i = children.length - 1; i >= 0; i--) {
children[i].classList.remove('active');
}
}
addSorting() {
const buttonGroup = document.querySelector('.sort-options');
if (!buttonGroup) {
return;
}
buttonGroup.addEventListener('change', this._handleSortChange.bind(this));
}
_handleSortChange(evt) {
// Add and remove `active` class from buttons.
const buttons = Array.from(evt.currentTarget.children);
buttons.forEach((button) => {
if (button.querySelector('input').value === evt.target.value) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
// Create the sort options to give to Shuffle.
const { value } = evt.target;
let options = {};
function sortByDate(element) {
return element.getAttribute('data-date-created');
}
function sortByTitle(element) {
return element.getAttribute('data-title').toLowerCase();
}
if (value === 'date-created') {
options = {
reverse: true,
by: sortByDate,
};
} else if (value === 'title') {
options = {
by: sortByTitle,
};
}
this.shuffle.sort(options);
}
// Advanced filtering
addSearchFilter() {
const searchInput = document.querySelector('.js-shuffle-search');
if (!searchInput) {
return;
}
searchInput.addEventListener('keyup', this._handleSearchKeyup.bind(this));
}
/**
* Filter the shuffle instance by items with a title that matches the search input.
* #param {Event} evt Event object.
*/
_handleSearchKeyup(evt) {
const searchText = evt.target.value.toLowerCase();
this.shuffle.filter((element, shuffle) => {
// If there is a current filter applied, ignore elements that don't match it.
if (shuffle.group !== Shuffle.ALL_ITEMS) {
// Get the item's groups.
const groups = JSON.parse(element.getAttribute('data-groups'));
const isElementInCurrentGroup = groups.indexOf(shuffle.group) !== -1;
// Only search elements in the current group
if (!isElementInCurrentGroup) {
return false;
}
}
const titleElement = element.querySelector('.picture-item__title');
const titleText = titleElement.textContent.toLowerCase().trim();
return titleText.indexOf(searchText) !== -1;
});
}
}
export default Demo;
I would be extremely thankful for any help, sincei have been stuck on this for days!
Like #charlietfl said, DOMContentLoaded has already occurred on initial page load, and will not be triggered again when you navigate between routes in your SPA.
Try removing the listener:
componentDidMount () {
//document.addEventListener('DOMContentLoaded', () => {
// window.demo = new Demo(document.getElementById('grid'));
//});
window.demo = new Demo(document.getElementById('grid'));
}

How to show different DIV content based on current Index using React?

How can I show different DIV content based on the current index of a slide? This is a component which I'm looping through a MAP and the image, content, and id is inside the DATA object.
What I'm trying to have here to show different HTML/Content based on the currentIndex how can i get this to work?
What am I doing wrong? Currently, it's displaying all the index slides on EACH slide.
Thanks in advance!
import React, { Component } from 'react';
// Components
import QuizSlide from '../Slider/Slide';
// import QuizMain from '../Quiz/QuizMain';
import LeftArrow from '../Arrows/LeftArrow';
import RightArrow from '../Arrows/RightArrow';
import Footer from '../Slider/Footer';
import QuizLogo from 'images/QuizLogo.svg';
// App Styles
import 'sass/root.scss';
export default class QuizSlider extends Component {
// The Constructor
constructor(props) {
super(props);
this.state = {
footerURL: 'http://www.google.nl',
footerText: 'Naar website STC',
copyright: 'Friends For Brands 2018',
currentIndex: 0,
translateValue: 0,
data: [
{index: 1, content: 'Ga voor grenzeloos', image: 'https://images.pexels.com/photos/219014/pexels-photo-219014.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=650&w=940'},
{index: 2, content: 'Sectoren', image: 'https://images.pexels.com/photos/259984/pexels-photo-259984.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=650&w=940'},
{index: 3, content: 'Wat wil jij?', image: 'https://images.pexels.com/photos/355952/pexels-photo-355952.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=650&w=940'},
{index: 4, content: 'Vlogs', image: 'https://images.pexels.com/photos/320617/pexels-photo-320617.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=650&w=940'},
{index: 5, content: 'Belangrijke data', image: 'https://images.pexels.com/photos/1181316/pexels-photo-1181316.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=650&w=940'}
]
}
}
// Functions
PrevSlide = () => {
if(this.state.currentIndex === 0) {
return this.setState({
currentIndex: 0,
translateValue: 0
})
}
// This will not run if we met the if condition above
this.setState(PrevState => ({
currentIndex: PrevState.currentIndex - 1,
translateValue: PrevState.translateValue + (this.slideWidth())
}));
}
NextSlide = () => {
const slideWidth = this.slideWidth();
// Exiting the method early if we are at the end of the images array.
// We also want to reset currentIndex and translateValue, so we return
// to the first image in the array.
if(this.state.currentIndex === this.state.data.length - 1) {
return this.setState({
currentIndex: 0,
translateValue: 0
})
}
// This will not run if we met the if condition above
this.setState(NextState => ({
currentIndex: NextState.currentIndex + 1,
translateValue: NextState.translateValue + -(slideWidth)
}));
}
slideWidth = () => {
return document.querySelector('.QuizSlide').clientWidth
}
// Render
render() {
return (
<div className="QuizSlider">
<div className="QuizLogo">
<img src={QuizLogo}/>
</div>
<LeftArrow PrevSlide={this.PrevSlide} />
<RightArrow NextSlide={this.NextSlide} />
<div className="slider-wrapper" style={{ transform: `translateX(${this.state.translateValue}px)` }}>
{
this.state.data.map((props, index) => (
<QuizSlide key={index} content={props.content} id={index + 1} image={props.image} />
))
}
</div>
<Footer url={this.state.footerURL} text={this.state.footerText} copyright={this.state.copyright} />
</div>
)
}
}
import React from 'react';
const QuizSlide = ({image, content, id}) => {
const currentIndexSlide = id;
if(currentIndexSlide === 1) {
<div className="slide-1">Show this data on 1.</div>
}
if(currentIndexSlide === 2) {
<div className="slide-2">Show this data on 2.</div>
}
if(currentIndexSlide === 3) {
<div className="slide-3">Show this data on 3.</div>
}
return (
<div className="QuizSlide" style={{backgroundImage: `url(${image})`}}>
<div className="QuizSlide--content">
<h2>{content}</h2>
{id}
</div>
</div>
)
}
export default QuizSlide;
In the return section which renders the HTML DOM, you are displaying the entire content. Every time the QuizSlide component is called on iterating the array through a map and hence all the data is displayed.
So, the restriction should be within the render section. The conditional rendering should be something like:
return (
<div className="QuizSlide" style={{backgroundImage: `url(${image})`}}>
<div className="QuizSlide--content">
<h2>{content}</h2>
{id}
{id === '1' &&
<div className="slide-1">
Show this data on 1.
</div>
}
{id === '2' &&
<div className="slide-2">
Show this data on 2.
</div>
}
</div>
</div>
)
Define a variable using let before your if statements, then assign a value to it inside those, displaying that inside your return.
const QuizSlide = ({image, content, id}) => {
const currentIndexSlide = id;
let slide;
if(currentIndexSlide === 1) {
slide = <div className="slide-1">Show this data on 1.</div>
}
if(currentIndexSlide === 2) {
slide = <div className="slide-2">Show this data on 2.</div>
}
if(currentIndexSlide === 3) {
slide = <div className="slide-3">Show this data on 3.</div>
}
return (
<div className="QuizSlide" style={{backgroundImage: `url(${image})`}}>
<div className="QuizSlide--content">
<h2>{content}</h2>
{id}
{slide}
</div>
</div>
)
}
export default QuizSlide;

Active classnames in menu on a one-page website using GatsbyJS

I'm using a single page template with GatsbyJS on which the menu scrolls to the different sections of the same page (#home, #about, #portfolio, etc). Is there a way to set an active classname on the links, highlighting the link the user is on?
Link provides two options for adding styles to the active link:
activeStyle — a style object that will only be applied when the current item is active
activeClassName — a class name that will only be added to the Link when the current item is active
Follow official docs: https://www.gatsbyjs.org/docs/gatsby-link/#add-custom-styles-for-the-currently-active-link
import React from "react"
import { Link } from "gatsby"
const SiteNavigation = () => (
<nav>
<Link
to="/"
{/* This assumes the `active` class is defined in your CSS */}
activeClassName="active"
>
Home
</Link>
<Link
to="/about/"
activeStyle={{ color: "red" }}
>
About
</Link>
</nav>
)
You can set the activeStyle or activeClassName prop to add styling attributes to the rendered element when it matches the current URL, and Gatsby also supports React Router's props exact, strict, isActive, and location. If any of these props are set, then React Router's NavLink component will be used instead of the default Link.
Example:
import Link from "gatsby-link"
render () {
<div>
<Link
to="/another-page/"
activeStyle={{
color: 'red'
}}
innerRef={(el) => { this.myLink = el }}
>
Another page
</Link>
</div>
}
Visit https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-link
I did it the hard way as I couldn't find another solution:
import React, { Component } from 'react';
import './Menu.css';
class Menu extends Component {
constructor(props) {
super(props)
this.state = {
home: true,
about: false,
portfolio: false
}
this.handleActive = this.handleActive.bind(this)
}
handleActive(button) {
switch (button) {
case 'home':
this.setState({
home: true,
about: false,
portfolio: false
});
break;
case 'about':
this.setState({
home: false,
about: true,
portfolio: false
});
break;
case 'portfolio':
this.setState({
home: false,
about: false,
portfolio: true
});
break;
default: break;
}
}
render() {
return (
<div id="nav-wrap">
<nav>
<input type="checkbox" id="checkbox1" />
<label htmlFor="checkbox1">
<ul className="menu first">
<li><a
className={this.state.home ? 'active' : null}
onClick={() => this.handleActive('home')}
href="#home">HOME</a></li>
<li><a
className={this.state.about ? 'active' : null}
onClick={() => this.handleActive('about')}
href="#about">ABOUT МЕ
</a></li>
<li><a
className={this.state.portfolio ? 'active' : null}
onClick={() => this.handleActive('portfolio')}
href="#portfolio">PORTFOLIO</a></li>
</ul>
<span className="toggle">☰</span>
</label>
</nav>
</div>
)
}
}
export default Menu;
As brooksrelyt noted in the comment above, you can easily use react-scrollspry to add a unique class name to the hash link that has been clicked. Here is how I use it:
import { Text } from 'rebass/styled-components'
import Scrollspy from 'react-scrollspy'
<Scrollspy
items={['home', 'features', 'faq']}
currentClassName="isCurrent"
>
<Link to="#home">Home</Link>
<Link to="#features">Features</Link>
<Link to="#faq">FAQ</Link>
</Scrollspy>
<Text id="home">Home</Text>
<Text id="features" mt={'400vh'}>Features</Text>
<Text id="faq" mt={'150vh'}>FAQ</Text>
In short, you wrap your links with a Scrollspy component and include (at the very least) two mandatory props: items and currentClassName.
items is an array of the hash names (without the hash character)
currentClassName is the name of the class you want to add to the selected link.
NOTE: I included the rebass Text component because it did not work properly for me when I used a simple div. You should read the documentation for how to use it in your particular case - as there are other props that may be needed in different situations.
I did a website some weeks ago with this feature. I created a function to know what section are the active and put it on the state in react. This function is called everytime the user move the scrollbar.
In render, I change de className of the elements depending the element of the state. In every section/element that i want to track I put an ID.
Util function:
/**
* Helper function to get an element's exact position
* #param {element} element
* #return {x,y}
*/
export function getPosition(el) {
var xPos = 0;
var yPos = 0;
while (el) {
if (el.tagName == "BODY") {
// deal with browser quirks with body/window/document and page scroll
var xScroll = el.scrollLeft || document.documentElement.scrollLeft;
var yScroll = el.scrollTop || document.documentElement.scrollTop;
xPos += (el.offsetLeft - xScroll + el.clientLeft);
yPos += (el.offsetTop - yScroll + el.clientTop);
} else {
// for all other non-BODY elements
xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPos += (el.offsetTop - el.scrollTop + el.clientTop);
}
el = el.offsetParent;
}
return {
x: xPos,
y: yPos
};
}
On React component:
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
this.handleScroll();
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll() {
const {inAnimation} = this.state;
let activeElement = false;
this.props.data.items.forEach((value,i) => {
let element = document.getElementById(value.url.substring(1));
if(getPosition(element).y <= 0){
activeElement = value.url;
}
});
this.setState({
activeElement
});
}
}
render(){
...
items.map((item, i) => {
return <li key={i}>
<a className={'menu-item' + (activeElement == item.url ? ' active': '')}>{item.title}</a>
</li>;
});
...
}

Open a modal from a component

I am working on a component where I need to display and hide a modal.
this is what I have in the render method in React
<div style={{visibility : this.state.displayModal}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._openModal}>CLICK</button>
and here is the function
_openModal = () => {
if (this.state.displayModal === 'hidden') {
this.setState({
displayModal : 'visible',
})
} else {
this.setState({
displayModal : 'hidden',
})
}
}
the main concern I have, is, how to set the state in a more elegant way, or this should be the way to do it ?
here the full code
constructor (props) {
super(props);
this.state = {
displayModal : 'hidden',
}
}
render () {
return (
<div style={{visibility : this.state.displayModal}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._openModal}>CLICK</button>
)
}
_openModal = () => {
if (this.state.displayModal === 'hidden') {
this.setState({
displayModal : 'visible',
})
} else {
this.setState({
displayModal : 'hidden',
})
}
}
so, what should be the way to this pop up in a React way.
I think it's a good way to do it. But it will be more concise if you make displayModel a boolean:
_toggleModal = () => this.setState({displayModal: !this.state.displayModal})
On a complex page using hidden will be a performance issue. Try something like this instead;
render() {
var returnIt;
if (this.state.hide) {
returnIt = null;
} else {
returnIt = (
<div style={{visibility : this.state.displayModal}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._openModal}>CLICK</button>
)
}
return (returnIt);
}
This is just a personal opinion, but I think a better UX would be that the button should only be used to open the modal; and the modal should be closed by either clicking the X in the modal (if there is) or when you click anywhere outside the modal.
That said if you definitely need the button to toggle between the 2 states, how about something like this?
constructor (props) {
super(props);
this.state = {
displayModal : false
}
}
render () {
return (
<div style={{visibility : this.state.displayModal === true ? 'visible' : 'hidden'}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._toggleModal}>CLICK</button>
)
}
_toggleModal = () => {
const current = this.state.displayModal;
this.setState({
displayModal : !current
});
}
Using https://github.com/fckt/react-layer-stack you can do like so:
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...

Categories

Resources