scroll to behavior with react router - javascript

Is it possible with react router to maybe specify that Link should scroll to a component instead of rendering it? At the moment my components are rendered all at once (in a slightly long page). I have a nav bar and I would like when a user clicks on a Link in nav to scroll up/down to the appropriate component.

I managed to solve my problem with a little help from this post here.
He's using es6, an es5 version of it will look like this:
const hashLinkScroll = function () {
const {hash} = window.location;
if (hash !== '') {
const milliseconds = 0;
setTimeout(function () { // eslint-disable-line prefer-arrow-callback
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView();
}
}, milliseconds);
}
}
Watch out if you are using a function declaration rather than a function expression. The latter must be defined before it is called.
As for your router, you will have something like:
<Router history={browserHistory} onUpdate={hashLinkScroll}>your routes go here</Router>

Related

element.scrollTo / scrollIntoView don't scroll all the way sometimes on React app

I've written a custom hook that takes a list of refs and scrolls to the element if there's a query telling it to. I have nav links that should scroll to their respective elements.
It works as intended most of the time, but randomly sometimes it just won't scroll accurately to that element, won't scroll all the way.
I am stuck.
function useScrollOnRedirect(elements: React.RefObject<HTMLElement>[]) {
// ?scrollto=about
const { search } = useLocation()
useEffect(() => {
const parsedSearch = new URLSearchParams(search)
const query = parsedSearch.get("scrollTo")
if (query) {
const element = elements.find(el => el?.current?.id === query)?.current
if (element) {
element.scrollIntoView({behavior: "smooth"})
}
}
}, [elements, search])
}
Tried element.scrollTo, element.scrollIntoView, window.scrollTo with refs and getElementbyid, useLayoutEffect....

How to force new state in React (Hooks) for textarea when clicking on another element?

codesandbox.io sandbox
github.com repository
I am creating this small Wiki project.
My main component is Editor(), which has handleClick() and handleModeChange() functions defined.
handleClick() fires when a page in the left sidebar is clicked/changed.
handleModeChange() switches between read and write mode (the two icon buttons in the left sidebar).
When in read mode, the clicking on different pages in the left sidebar works properly and changes the main content on the right side.
However, in write mode, when the content is echoed inside <TextareaAutosize>, the content is not changed when clicking in the left menu.
In Content.js, I have:
<TextareaAutosize
name="textarea"
value={textareaValue}
minRows={3}
onChange={handleMarkdownChange}
/>
textareaValue is defined in Content.js as:
const [textareaValue, setTextareaValue] = useState(props.currentMarkdown);
const handleMarkdownChange = e => {
setTextareaValue(e.target.value);
props.handleMarkdownChange(e.target.value);
};
I am unsure what is the correct way to handle this change inside textarea. Should I somehow force Editor's child, Content, to re-render with handleClick(), or am I doing something completely wrong and would this issue be resolved if I just changed the definition of some variable?
I have been at this for a while now...
You just need to update textareaValue whenever props.currentMarkdown changes. This can be done using useEffect.
useEffect(() => {
setTextareaValue(props.currentMarkdown);
}, [props.currentMarkdown]);
Problem:
const [textareaValue, setTextareaValue] = useState(props.currentMarkdown);
useState will use props.currentMarkdown as the initial value but it doesn't update the current state when props.currentMarkdown changes.
Unrelated:
The debounce update of the parent state can be improved by using useRef
const timeout = useRef();
const handleMarkdownChange = (newValue) => {
if (timeout.current) {
clearTimeout(timeout.current);
}
timeout.current = setTimeout(function () {
console.log("fire");
const items = [];
for (let value of currentData.items) {
if (value["id"] === currentData.active) {
value.markdown = newValue;
value.unsaved = true;
}
items.push(value);
}
setCurrentData({ ...currentData, items: items });
}, 200);
};
using var timeout is bad because it declares timeout on every render, whereas useRef gives us a mutable reference that is persisted across renders

How to provide window width to all React components that need it inside my app?

So I've got this hook to return the windowWidth for my App components. I'll call this Option #1.
import {useEffect, useState} from 'react';
function useWindowWidth() {
const [windowWidth,setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWindowWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowWidth;
}
export default useWindowWidth;
And right now I'm basically using it on every component that depends on the window width to render, like:
function Component(props) {
const windowWidth = useWindowWidth();
return(
// RETURN SOMETHING BASED ON WINDOW WIDTH
);
}
And since the hook has an event listener for the resize events, the component stays responsive even after window resizes.
But I'm worried that I'm attaching a new listener for every component that uses that hook and it might slow things down at some point. And I've though of other approach:
Option #2
I use the useWindowWidth() hook only one time, inside a top level component like <App/> and I'll provide the windowWidth value down the chain via context.
Like:
function App() {
const windowWidth = useWindowWidth();
return(
<WindowWidthContext.Provider value={windowWidth}>
<Rest_of_the_app/>
</WindowWidthContext.Provider>
);
}
And then, every component that needs it could get it via:
function Component() {
const windowWidth = useContext(WindowWidthContext);
return(
// SOMETHING BASED ON WINDOW WIDTH
);
}
QUESTION
Am I right in being bothered by that fact that I'm setting up multiple resize listeners with Option #1 ? Is Option #2 a good way to optmize that flow?
If your window with is used by so many components as you mentioned, you must prefer using context. As it reads below:
Context is for global scope of application.
So, #2 is perfect choice here per react.
First approach #1 might be good for components in same hierarchy but only up-to 2-3 levels.
I'm not sure if adding and removing event listeners is a more expensive operation than setting and deleting map keys but maybe the following would optimize it:
const changeTracker = (debounceTime => {
const listeners = new Map();
const add = fn => {
listeners.set(fn, fn);
return () => listeners.delete(fn);
};
let debounceTimeout;
window.addEventListener('resize', () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(
() => {
const width=window.innerWidth;
listeners.forEach(l => l(width))
},
debounceTime
);
});
return add;
})(200);
function useWindowWidth() {
const [windowWidth, setWindowWidth] = useState(
() => window.innerWidth
);
useEffect(
() =>//changeTracker returns a remove function
changeTracker((width) =>
setWindowWidth(width)
),
[]
);
return windowWidth;
}
As HMR said in an above thread, my solution was to use redux to hold the width value. With this strategy you only need one listener and you can restrict how often you update with whatever tool you like. You could check if the width value is within the range of a new breakpoint and only update redux when that is true. This only works if your components dont need a steady stream of the window width, in that case just debounce.

React and Matchmedia

I'm using react context API in component did mount to set methods. I'd also like to use the media query there and set a method to open or close the sidenav depending on screen size.
Something like this
componentDidMount() {
let context = this.context;
let path = this.props.pageContext && this.props.pageContext.path;
context.setSidenavLeaf(newPath)
// Below this is where I'd like to use the media query to set the sidenavOPen to false. Just not sure how to achieve that
const match = window.matchMedia(`(max-width: 768px)`)
if(match {
context.setSidenavOpen(false)
}
}
Kind of confused about how to achieve something like this. I want to call the method and set it at a specific media break point in my component did mount. Which is using react router path prop. So if I hit that specific url rendered by that component and the screen size is such, close the sidenav else leave it open.
You need to listen for resize event:
componentDidMount() {
let context = this.context;
let path = this.props.pageContext && this.props.pageContext.path;
context.setSidenavLeaf(newPath);
// Below this is where I'd like to use the media query to set the sidenavOPen to false. Just not sure how to achieve that
this.checkWidth = () => {
const match = window.matchMedia(`(max-width: 768px)`);
if (match) {
context.setSidenavOpen(false);
}
};
this.checkWidth();
window.addEventListener("resize", this.checkWidth);
}
componentWillUnmount() {
window.removeEventListener("resize", this.checkWidth);
}
or add listener to the media query itself:
componentDidMount() {
let context = this.context;
let path = this.props.pageContext && this.props.pageContext.path;
context.setSidenavLeaf(newPath);
// Below this is where I'd like to use the media query to set the sidenavOPen to false. Just not sure how to achieve that
this.match = window.matchMedia(`(max-width: 768px)`);
this.checkWidth = (e) => {
if (e.matches) {
context.setSidenavOpen(false);
}
};
this.checkWidth(this.match);
this.match.addListener(this.checkWidth);
}
componentWillUnmount() {
this.match.removeListener(this.checkWidth);
}
for functional components, there's a hook on github that will do this for you https://www.npmjs.com/package/react-simple-matchmedia

How to organize actions that don't modify state?

let's say I have a react component like this:
class App extends Component {
print = () => {
const { url } = this.props
const frame = document.createElement('iframe')
frame.addEventListener('load', () => {
const win = frame.contentWindow
win.focus()
win.print()
win.addEventListener('focus', () => document.body.removeChild(frame))
})
Object.assign(frame.style, {
visibility: 'hidden',
position: 'fixed',
right: 0,
bottom: 0
})
frame.src = url
document.body.appendChild(frame)
}
}
Basically, clicking a button calls the print function in the browser for the user. In a situation like this, do I still make this into a redux action like DO_PRINT that doesn't actually do anything to my redux state or do I just not bother with it?
For your particular example, I would avoid creating a Redux action as there is no need for that DO_PRINT to update any state if it is only calling window.print().
In fact, assuming you're creating a "Print button" component, I would redefine this as a dumb component. (See differences between presentationl and container components.)
import React from ‘react’;
const PrintButton = () => {
const onClick = () => {
window.print();
};
return <button onClick={onClick}>Click Me</button>
};
export default PrintButton;
FYI, the code above might not be the most efficient way of declaring event handlers for stateless components as the function is potentilly called each time the component is rendered. There might be better (more efficient) ways (described in another SO question) but that's beyond this question.

Categories