How to replace item while scrolling? - javascript

I need to change navbar when scrolling a page. How to catch the moment when to change it? How to do it right, in accordance with the concepts of React? As far as I know, use getElementById is that bad tone?
const useState = React.useState
const useEffect = React.useEffect
const Component = () => {
const [topNavbarHide, setTopNavbarHide] = useState(true);
useEffect(() => {
window.addEventListener('scroll', function () {
let navbarSize = document.getElementById('navbar').offsetHeight;
console.log("navbarSize " + navbarSize + "px");
let scrollTop = document.documentElement.scrollTop;
console.log("scrollTop " + scrollTop);
if (scrollTop > navbarSize) {
setTopNavbarHide(false)
} else {
setTopNavbarHide(true)
}
console.log(topNavbarHide);
});
});
return (
<div>
<div id={"navbar"} className="navbar">
<div
className={(topNavbarHide) ? "topNavbar" : "topNavbar hide"}>topNavbar
</div>
<div className="bottomNavbar">bottomNavbar</div>
</div>
<div className="box"></div>
<div className="box1"></div>
<div className="box2"></div>
<div className="box"></div>
<div className="box1"></div>
</div>
)
};
ReactDOM.render(
<Component/>,
document.getElementById('root')
);
https://codepen.io/slava4ka/pen/wvvGoBX

It is perfectly fine to add an event listener, in the way that you are currently doing it, to a React hook. The way that you are doing it is the correct way.

There is another, more simpler way with react-waypoint. You can place invisible waypoints on your screen and it can trigger events when waypoint enter or leave screen.
For example:
const Component = () => {
const [topNavbarHide, setTopNavbarHide] = useState(true);
return (
<div>
<div id={"navbar"} className="navbar">
<div className={topNavbarHide ? "topNavbar" : "topNavbar hide"}>
topNavbar
</div>
<div className="bottomNavbar">bottomNavbar</div>
</div>
<Waypoint
onEnter={() => setTopNavbarHide(true)}
onLeave={() => setTopNavbarHide(false)}
/>
<div className="box" />
<div className="box1" />
<div className="box2" />
<div className="box" />
<div className="box1" />
</div>
);
};
It is basically working like your example.
https://codesandbox.io/s/hungry-hodgkin-5jucl

Related

Third condition in ternery operator doesn't render when condition is met

Trying to figure out where I am going wrong with this ternery.
It isn't rendering the last element (blurredscreen) if authStatus !== "authenticated".
return (
<>
<div key={"key-" + id}>
{isOpen && (
<div>
{authStatus === "authenticated" &&
(header.x && header.x.t === "abc" ? (
<div
onClick={(e) => {
e.stopPropagation();
}}
>
<ComponentOne />
</div>
) : header.x && header.x.t === "def" ? (
<div
onClick={(e) => {
e.stopPropagation();
}}
>
<ComponentTwo />
</div>
) : ( //this is the part that doesn't display when condition is met
authStatus !== "authenticated" &&
!header.x && (
<div>
<img
src={blurredScreen}
/>
<button
onClick={handleSignInClick}
>
<span style={{ textAlign: "left" }}>Login </span>
</button>
</div>
)
))}
</div>
)}
</div>
</>
);
Overuse of tenery operator in JSX can lead to really confusing code and makes it hard to debug. The posters question is a good example, there are only 3 conditional routes and the code looks very confusing, as you can imagine this only gets more complicated with 4,5+.. etc.
The good news, you can still use if else inside React, just assign your JSX to a local variable and render this.
Below is a simple example that flips between 3 states, and as you can see is much easier to reason with.
const {useEffect, useState, Fragment} = React;
function Demo() {
const [s, setS] = useState(0);
useEffect(() => {
const t = setInterval(() => setS(c => c +1), 1000);
return () => { clearInterval(t) }
}, []);
let render = null;
let d = s % 3;
if (d === 0) {
render = <div>This is 0</div>
} else if (d ===1) {
render = <div>This is One</div>
} else {
render = <div>Everything else</div>
}
//Fragment used here, because SO Snippet
//do not transpile <>....</>
return <Fragment>{render}</Fragment>
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Demo/>);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
Also notice I set let render = null;, this is deliberate, null is a valid JSX type that renders nothing. So if for some reason no code paths are met, React will render nothing instead of throwing an error. Of course in the above example that's not going to happen, but it's good habit to get into. If your on React 18, you can now leave out the null. https://blog.saeloun.com/2022/04/14/react-18-allows-components-to-render-undfined.html

My React code get crash some time but not always

I am building a project in which there is only a single question and it changes when pressing the refresh button. Everything is going well but sometimes the code crashes and the page gets blank (as shown in attached image) on pressing the refresh button.
import React from "react"
import questionsData from '../questionsData.js'
function Content(){
const [question, setQuestion] = React.useState({
heading:"",
hints : []
})
const [refresh, setRefresh] = React.useState(0)
React.useEffect(function(){
if(questionsData){
const randomNo = Math.floor(Math.random() * questionsData.length) + 1
let headinggg = questionsData[randomNo].heading
let hintsss = questionsData[randomNo].hints
setQuestion({
heading: headinggg,
hints: hintsss
})
}else{
document.write("loading")
}
},[refresh])
const sugg = question.hints.map((hint) => {
return(
<li>{hint}</li>
)
})
function renderQuestion(){
setRefresh(refresh + 1)
}
return(
<div className="content">
<div className="question">
<h4>{question.heading}</h4>
<br />
<span>You should say:</span>
<ul>
{sugg}
</ul>
</div>
<div className="action">
<div className="record">
<img src="images/211859_mic_icon.svg" alt="" />
</div>
<div className="refresh" onClick={renderQuestion}>
<img src="images/3994399_refresh_reload_repeat_rotate_sync_icon.svg" alt="" />
</div>
</div>
</div>
)
}
export default Content
You should remove the + 1 from the randomNo calculation, otherwise it could case an IndexOutOfBounds error:
const randomNo = Math.floor(Math.random() * questionsData.length);

H1 tag value not showing when page loads up in React

Problem: when the page first loads up, the PageHeader H1 tag shows a blank value instead of "New Page". Why does this happen and how can I make it show "New Page"?
In Page.js
const [pageTitle, setPageTitle] = useState('New Page');
const pageTitleChangeHandler = (e) => {
setPageTitle(e.target.innerText);
}
return (
<PageHeader title = {pageTitle} onChange = {pageTitleChangeHandler}></PageHeader>
);
In PageHeader.js
return (
<div className = "page-header">
<h1
className = "page-header-text"
contentEditable = "true"
onInput = {props.onChange}
value = {props.title}>
</h1>
</div>
);
I think you should remove value attribute from h1 tag and set dangerouslySetInnerHTML instead like
import React from "react";
export default function PageHeader(props) {
return (
<div className="page-header">
<h1
className="page-header-text"
contentEditable="true"
onInput={props.onChange}
dangerouslySetInnerHTML={{ __html: props.title }}
>
</h1>
</div>
);
}
If you want New Page to show up on page load, you need to change the argument to setPageTitle from e.target.innerText to e.target.value, and add {props.title} in the h1 tag.
Then you can change onInput to onKeyDown.
This alone would delete New Page from the header once you edit. To avoid this, simply change title={pageTitle} to title={"New Page"}.
const [pageTitle, setPageTitle] = useState('New Page');
const pageTitleChangeHandler = (e) => {
// This will set the value prop
setPageTitle(e.target.value);
}
return (
<PageHeader
title={"New Page"}
onChange={pageTitleChangeHandler}
></PageHeader>
);
}
const PageHeader = (props) => {
return (
<div className="page-header">
<h1
className="page-header-text"
contentEditable="true"
onKeyDown={props.onChange}
value={props.title}
>
{props.title}
</h1>
</div>
);

Why can I only fire this function once?

I have this function:
const sliderTextChange = document.getElementsByClassName('slider') // text change
const changeSliderText = change => {
const sliderLeft = document.getElementsByClassName('switch-left')
const sliderRight = document.getElementsByClassName('switch-right')
for (let i = 0; i < change.length; i++) {
change[i].addEventListener('click', () => {
sliderRight[i].style.display = 'flex';
sliderLeft[i].style.display = 'none';
});
}
}
changeSliderText(sliderTextChange);
This is one of the many sliders on the website:
<div class="flex-column">
<h3>Text Colour</h3>
<div class="slider">
<div class="slider-back"></div>
<div class="slider-circle"></div>
</div>
<h3 class="switch-left">White</h3>
<h3 class="switch-right">Black</h3>
</div>
This function is quite a lot like many other functions in my code but they're only firing once. AKA I fire the event listener and but then I can't fire it again.
What's the issue here?
I have tried to simplify your code and keep the scope to be modular and reusable view.
function bindEvent() {
const sliderList = document.querySelectorAll('.slider');
[...sliderList].forEach((slider) => slider.addEventListener('click', () => {
const left = slider.parentElement.querySelector('.switch-left');
const right = slider.parentElement.querySelector('.switch-right');
const leftDisplay = left.style.display || 'flex';
const rightDisplay = right.style.display || 'none';
left.style.display = rightDisplay;
right.style.display = leftDisplay;
}, false));
}
window.onload = bindEvent;
<div>
<button class="slider"> - SLIDER 1 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
<div>
<button class="slider"> - SLIDER 2 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
<div>
<button class="slider"> - SLIDER 3 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
<div>
<button class="slider"> - SLIDER 4 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
Parameters you have chosen for your function are not really intuitive and make your example more complex.
We use querySelector, it's nicer to read but if you prefer speed, just go for getElementsByClassName, it also works on any DOM element.

onClick event returning Every Modal instead of a Single Modal

I'm trying to build a Jeopardy like game using React and Redux. I currently have an onClick event set to each li, but whenever I click on it, I get every Modal to pop up instead of the one that is attached to that li item. I have my code separated in different files but I believe these two files are the only ones I need to show:
const ModalView = React.createClass({
pass: function(){
console.log('pass clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
submit: function(){
console.log('submit clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let question = this.props.question
let category = this.props.category
let answer = this.props.answer
let val = this.props.value
return (
<div>
<div className="modal">
<p>{category}</p>
<p>{question}</p>
<p>{answer}</p>
<p>{val}</p>
<input
type="text"
placeholder="type in your answer">
</input>
<button onClick={this.submit}>Submit</button>
<button onClick={this.pass}>Pass</button>
</div>
</div>
)
}
})
and ValuesView
const ValuesView = React.createClass({
modalPopUp: function(value){
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let showClass = "show-content"
let hideClass = "hide-content"
if (this.props.modal){
showClass = "hide-content"
hideClass = "show-content"
}
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp} key={i}>$600</li>
</div>
}
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})
How would I go about only getting the corresponding Modal to display instead of every one? Thanks!!
If you just want to code real Modal I suggest you to use some already implemented component like https://github.com/reactjs/react-modal (I'm not saying is mandatory, nor even whit the example I suggest, could be other)
I think that store.dispatch({type:"MODAL_TOGGLE"}) in some way toggle modal prop between true and false. Then you just use that flag to toggle a class to show or hide content I guess (I would need to see you css).
The problem with this approach is (apart that is not the best way to do this for many reasons) that you are using the same class for every item in your clues array.
In some way you need to store which is the "modal" you want to show, and then in the render, just apply the show class to this item.
Maybe:
const ValuesView = React.createClass({
modalPopUp: function(index){
return function (event) {
store.dispatch({
type:"MODAL_TOGGLE",
payload: {
modalIndex: index // Save modalIndex prop
}
})
}
},
render: function(){
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp(i)} key={i}>$600</li>
</div>
}
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp(i)} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})

Categories

Resources