Im starting with react and im trying to make a horizontal scrolling page. It seems to work just fine except for one thing, of which i'm pretty certain i'm missing some React logic for this.
I use a targetContainer div with in it, several pages (fullscreen) and a Navbuttons class to move it around.
In my code below i use a 'NavButtons' functional component that sets the targetContainers 'left' value.
But when I reload the page with F5, my page stays on set style (e.g. left:-300%) but pageCounter goes back to 0, breaking the nav buttons...
I'm pretty certain its because i'm using the css-style but what's the right/best way to solve this?
import React, { useEffect, useState } from 'react';
const NavButtons = (props) => {
const maxCount = props.maxCount;
const [pageCounter, setPageCounter] = useState(0);
const scrollPrev = function () {
if (pageCounter > 0) {
setPageCounter(pageCounter - 1);
}
}
const scrollNext = function () {
if (pageCounter < (maxCount - 1)) {
setPageCounter(pageCounter + 1);
}
}
useEffect(() => {
props.targetContainer.current.style.left = -((pageCounter) * 100) + 'vw';
}, [pageCounter, props.targetContainer]);
useEffect(() => {
setToZero();
}, []);
const setToZero = function () {
setPageCounter(0);
props.targetContainer.current.style.left = 0;
}
return (
<div className="NavButtons">
<button onClick={scrollPrev}>Prev</button>
<button onClick={scrollNext}>Next</button>
</div>
)
}
export default NavButtons;
Here is a stackblitz,
https://react-zyvu7o.stackblitz.io/
Edit on:
https://stackblitz.com/edit/react-zyvu7o?file=src/components/Navbuttons.js
It 'unfortunately' works normal on stackblits, but not on my localhost... :(
I'm fairly confident this only occurs due to browser caching & hot reloading, which is why it's working in your example and not locally.
Related
I'm a bit new to React so I may be missing something obvious, but I've used similar ways of rendering HTML from a CMS into a partially React DOM (just the component, everything else is provided via jsp.) I can see the data passing in correctly, but it seems like the ReactDOM.render isn't doing anything. Any idea what I'm missing/doing wrong?
import ReactDOM from 'react-dom';
/**** Component ****/
import FulfillmentInfoDrawer from "REACT_COMPONENTS/dialogs/fulfillmentInfoDrawer/FulfillmentInfoDrawer";
/**** Utilities ****/
import {Helper} from 'REACT_UTILITIES/Helper';
function fulfillmentFlyout () {
Helper.waitForElm('#react-fulfillmentFlyout').then(() =>{
let insertFlyoutDiv = document.getElementById('react-fulfillmentFlyout'),
fulfillmentType = insertFlyoutDiv.dataset.fulfillmenttype,
flyoutOne = Helper.decodeHTMLEntities(insertFlyoutDiv.getElementsByClassName('flyoutOne')[0].outerHTML),
flyoutTwo = Helper.decodeHTMLEntities(insertFlyoutDiv.getElementsByClassName('flyoutTwo')[0].outerHTML),
flyoutTrigger = Helper.decodeHTMLEntities(insertFlyoutDiv.getElementsByClassName('flyoutTwoTrigger')[0].outerHTML);
// console.log('Params',insertFlyoutDiv, fulfillmentType, flyoutOne, flyoutTwo, flyoutTrigger);
if(insertFlyoutDiv && fulfillmentType) {
const getFulfillmentInfoDrawer = () => {
return <FulfillmentInfoDrawer drawerType={fulfillmentType} drawer1Content={flyoutOne}
drawer2Content={flyoutTwo} drawer2Trigger={flyoutTrigger}
oldStyleFlyout={true}/>;
}
console.log('render', getFulfillmentInfoDrawer());
ReactDOM.render(<FulfillmentInfoDrawer drawerType={fulfillmentType} drawer1Content={flyoutOne}
drawer2Content={flyoutTwo} drawer2Trigger={flyoutTrigger}
oldStyleFlyout={true}/>, document.getElementById('react-fulfillmentFlyout'));
}
});
}
window.addEventListener('fulfillmentFlyout:BCC', fulfillmentFlyout);
With draft-js and a styled component, I made an inline input intended for a calculator. When I type into it with a keyboard, it works as expected:
When I press the plus button, a "+" is added to the text, but the view doesn't scroll:
Here's the behavior and the code in a codesandbox:
https://codesandbox.io/s/charming-brattain-mvkj3?from-embed=&file=/src/index.js
How can I get the view to scroll when text is added programmatically like that?
At long last, I figured out a solution. I added this to the calculator input component:
const shell = React.useRef(null);
const [scrollToggle, setScrollToggle] = React.useState(
{ value: false }
);
React.useEffect(() => {
const scrollMax = shell.current.scrollWidth - shell.current.clientWidth;
shell.current.scrollLeft += scrollMax;
}, [scrollToggle]);
const scrollToEnd = () => {
setScrollToggle({ value: !scrollToggle.value });
};
Then at the end of the insertChars function I added scrollToEnd();.
And I set ref={shell} on <InputShell>
I need help with my code. The thing I want create is to change className according to page url
So when I scroll or go to page /kontakt I want to change class from "hamburger" to "hamburger active".
I also tried regex. Any ideas?
Here is code:
const HamMenu = ()=> {
const [sidebar, setSidebar] = useState(false)
const [burger, setBurger] = useState(false)
const url = window.location.href;
const showSidebar = () => setSidebar(!sidebar)
const changeColor = () => {
if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
setBurger(true);
} else {
setBurger(false);
}
}
window.addEventListener('scroll', changeColor);
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
onClick={showSidebar}
/>
</Link>
</div>
Dealing with window in Gatsby could be a little bit tricky because two fundamental reasons:
window object is only defined in the browser, so it will work perfectly under gatsby develop but you will need to add a "hack" to avoid a code-breaking in the gatsby build (because there's no window in the Node server).
Treating the window outside React ecosystem, may break the rehydration of the components. This means that React won't potentially know what components need to re-render on-demand, causing unmounted components, especially when navigating forward and backward using the browser's history.
There are a few workarounds to achieve what you're trying to do.
Gatsby, by default, provides a location prop in all top-level components (pages). So you can pass it to any child components at any time to change the class name based on its value:
const IndexPage = ({ location }) =>{
return <Layout>
<HamMenu location={location} />
<h1> some content</h1>
</Layout>
}
Then, in your <HamMenu> component:
const HamMenu = ({ location })=> {
const [sidebar, setSidebar] = useState(false)
const [burger, setBurger] = useState(false)
const url = window.location.href;
const showSidebar = () => setSidebar(!sidebar)
const changeColor = () => {
if((window.scrollY >= 60) || (url.indexOf("kontakt") > -1)){
setBurger(true);
} else {
setBurger(false);
}
}
useEffect(() => {
if(typeof window !== "undefined"){
const url = window.location.href
const changeColor = () => {
setBurger(window.scrollY >= 60 || url.contains("kontakt"))
}
window.addEventListener('scroll', changeColor)
return () => {
window.removeEventListener('scroll', changeColor)
}
}
}, [])
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars' location.pathname.includes("your-page")? ''some-class' : 'some-other-class' } >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
onClick={showSidebar}
/>
</Link>
</div>
I would suggest another approach to get the scroll position rather than using directly the window, using React-based approach to avoid what I was pointing before (How to add a scroll event to a header in Gatsby).
However, I've fixed your initial approach, wrapping it inside a useEffect with empty deps ([]). This function will be triggered once the DOM tree is loaded, to avoid the code-breaking window use that I was talking about. Alternatively to url.indexOf("kontakt") > -1 you may want to use url.includes("kontakt") which is way more readable.
Regarding the rest, it's quite self-explanatory. Destructuring the location props you get access to a bunch of data, the pathname property holds the page name so based on that, you can add a ternary condition wherever you want, such as location.pathname.includes("your-page") ? ''some-class' : 'some-other-class' (includes is more semantic in my opinion).
As you see, I've fixed your approach but I've also added a React/Gatsby-based one, choose what makes you feel comfortable.
React components rendering server-side (such as during gatsby build) do not have access to window, and in order to avoid breaking hydration, the first render needs to match what is rendered server-side. For these reasons, you'll want to use useEffect to make client-side changes that rely on window after the component mounts.
Note that this solution is going to perform rather poorly since changeColor is calling setBurger on each scroll event, which prompts the component to be re-rendered (even if the value is the same). You'll want to add a debounce or throttle routine to mitigate this.
const HamMenu = ()=> {
const [burger, setBurger] = useState(false)
useEffect(() => {
const url = window.location.href
const changeColor = () => {
setBurger(window.scrollY >= 60 || url.contains("kontakt"))
}
window.addEventListener('scroll', changeColor)
return () => {
window.removeEventListener('scroll', changeColor)
}
}, [])
return (
<StyledMenu>
<div>
<Link to="#" className={sidebar ? 'menu-bars open' : 'menu-bars'} >
<FontAwesomeIcon
icon={faBars}
size="2x"
className={burger ? 'hamburger active' : 'hamburger'}
/>
</Link>
</div>
</StyledMenu>
)
}
I am trying to build a countdown timer app with start/pause button using React hooks.
However, the interaction does not responds as expected.
As the screenshot of console.log shows here:
In Event 2, when pause button gets clicked, props.setIsPaused(true) should set isPaused true, but console.log shows false. However, the value of isPaused in parent component gets updated to true according to console.log.
In Event 3, when start button gets clicked, props.setIsPaused(false) should set isPaused false, but console.log shows true. However, the value of isPaused in parent component gets updated to false according to console.log.
In this case, countdown timer does not start, I need to click start button for 2nd time to have it finally start the countdown.
I am totally confused by these actions out of my expectations.
Any ideas or thoughts what I missed or mistakes I have?
Thank you.
Here is the code of the child/parent component performing timer countdown:
// Child Component
function TimerPanel(props) {
const launch = React.useRef();
var secLeft = parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec)
const startCountDown = () => {
props.setIsPaused(false) // Once start button is clicked, set isPaused false
console.log('props.isPaused inside startCountDown func: ' + props.isPaused) // This should be false
launch.current = setInterval(decrement, 1000)
}
function decrement() {
if (secLeft > 0 && !props.isPaused) {
console.log('props.isPaused inside decrement func: '+ props.isPaused)
secLeft--;
var minCountDown = Math.floor(secLeft / 60)
var secCountDown = Math.floor((secLeft % 60) * 100) / 100
props.setTimerNow({
...props.timerNow,
min: minCountDown,
sec: secCountDown})
}
}
const pauseCountDown = () => {
props.setIsPaused(true) // Once pause button is clicked, set isPaused true
console.log('props.isPaused inside pauseCountDown func: '+ props.isPaused) // This should be true
clearInterval(launch.current)
}
......
}
// Parent Component
function App() {
const [breakLength, setBreakLength] = React.useState(5)
const [sessionLength, setSessionLength] = React.useState(25)
const [title, setTitle] = React.useState('Session')
const initialTimer = {
min: 25,
sec: 0
}
const [timerNow, setTimerNow] = React.useState(initialTimer)
const [isPaused, setIsPaused] = React.useState(false)
console.log(timerNow)
console.log('isPaused in App: ' + isPaused)
..........
}
Issues
I think what may've been tripping you up (I know it was me) was the split of logic between the app component and the timer panel component. All the state was declared in App, but all the state was mutated/updated in children components. I think some state was also poorly named, which made working with it a little confusing.
I've directly answered your concerns about the reflected values of isPaused being "delayed" in the comments, but I wanted to see how I could help.
Suggested Solution
Move all the logic to manipulate the timer state into the same component where the state is declared, pass functions to children to callback to "inform" the parent to do some "stuff".
In the following example I was able to greatly reduce the burden of TimerPanel trying to compute the time to display and trying to update it and keep in sync with the paused state from the parent.
TimerPanel
Receives timer value, in seconds, and computes the displayed minutes and values since this is easily derived from state. Also receives callbacks to start/stop/reset the timer state.
function TimerPanel({
timerNow,
title,
startTimer,
pauseTimer,
resetTimer,
}) {
const minutes = Number(Math.floor(timerNow / 60))
const seconds = Number(Math.floor((timerNow % 60) * 100) / 100)
return (
<div className="timer-wrapper">
<div className="display-title">{title}</div>
<div className="display-digits">
{minutes.toString().padStart(2, "0")}:
{seconds.toString().padStart(2, "0")}
</div>
<div className="control-keysets">
<i className="fas fa-play-circle" onClick={startTimer}>
Play
</i>
<i className="fas fa-pause-circle" onClick={pauseTimer}>
Pause
</i>
<button className="reset" onClick={resetTimer}>
Reset
</button>
</div>
</div>
);
}
App
Simply timer state to be just time in seconds as this makes time management much simpler. Create handlers to start, pause, and reset the timer state. Use an useEffect hook to run the effect of starting/stopping the time based on the component state. Do correct state logging in an useEffect hook with appropriate dependency.
function App() {
const [breakLength, setBreakLength] = React.useState(5);
const [sessionLength, setSessionLength] = React.useState(25);
const [title, setTitle] = React.useState("Session");
const initialTimer = 60 * 25;
const [timerNow, setTimerNow] = React.useState(initialTimer);
const [isStarted, setIsStarted] = React.useState(false);
const timerRef = React.useRef();
const start = () => setIsStarted(true);
const pause = () => setIsStarted(false);
const reset = () => {
setIsStarted(false);
setTimerNow(initialTimer);
}
React.useEffect(() => {
console.log("isStarted in App: " + isStarted);
if (isStarted) {
timerRef.current = setInterval(() => setTimerNow(t => t - 1), 1000);
} else {
clearInterval(timerRef.current)
}
}, [isStarted]);
React.useEffect(() => {
console.log('timer', timerNow);
}, [timerNow])
return (
<div className="container">
<h1>Pomodoro Timer</h1>
<div className="group">
<div className="block">
<BreakLength
breakLength={breakLength}
setBreakLength={setBreakLength}
isPaused={!isStarted}
/>
</div>
<div className="block">
<SessionLength
sessionLength={sessionLength}
setSessionLength={setSessionLength}
timerNow={timerNow}
setTimerNow={setTimerNow}
isPaused={!isStarted}
/>
</div>
</div>
<div className="bottom-block">
<TimerPanel
title={title}
setSessionLength={setSessionLength}
timerNow={timerNow}
startTimer={start}
pauseTimer={pause}
resetTimer={reset}
/>
</div>
</div>
);
}
I left the BreakLength and SessionLength components intact as they don't appear to be utilized much yet.
Demo
I want my code to toggle a person handler, Before it was working but since I split into components, It seem to have broken.
Toggle happens on button click (see inside return statement <
button className={btnClass}
onClick={props.toggler}>Button</button>
Here is my entire cockpit.js file (inside src/components/cockpit/cockpit.js).
import React from 'react';
import classes from './cockpit.css';
const Ccockpit = (props) => {
const assignedClasses = [];
let btnClass = ''
if (props.cocPersonState) {
btnClass = classes.red;
console.log(".......")
}
if (props.cocperson <= 2) {
assignedClasses.push(classes.red)
}
if (props.cocperson <= 1) {
assignedClasses.push(classes.bold)
}
return(
<div className={classes.cockpit}>
<h1> Hi I am react App</h1>
<p className={assignedClasses.join(' ')}>hey </p>
<button className={btnClass}
onClick={props.toggler}>Button</button>
</div>
);
}
export default Ccockpit;
and inside App.js
return (
<div className={classes.App}>
<Ccockpit>
cocPersonState = {this.state.showPerson}
cocperson = {this.state.person.length}
toggler = {this.togglerPersonHandler}
</Ccockpit>
{person}
</div>
)
}
}
and this is my togglerpersonHandler code.
togglerPersonHandler = () => {
const doesShow = this.state.showPerson;
this.setState({
showPerson: !doesShow
});
}
I can't see to figure out that why it won't toggle and console.log/change color to red (It isn't changing the state). Can someone please review and figure out the mistake?
Your JSX still isn't right. Please review the JSX syntax with regards to giving it props/children.
You have this:
<Ccockpit>
cocPersonState = {this.state.showPerson}
cocperson = {this.state.person.length}
toggler = {this.togglerPersonHandler}
</Ccockpit>
But those values aren't children, they're properties. So they need to be in the opening tag, like this:
<Ccockpit
cocPersonState = {this.state.showPerson}
cocperson = {this.state.person.length}
toggler = {this.togglerPersonHandler}/>
Revisit some React tutorials to see how JSX should be structured and how it works.