How would I achieve this scroll background colour change effect? - javascript

Basically, assume I have 10 sections. Each have a different colour assigned to them for background colour.
When the user scrolls down from sections 1 through 10, I would like the tag background colour to change accordingly, depending which section is on screen.
Assuming the height of the viewport is 1000px, I would like the function to find out which section is currently at 800px out of 1000px, so the bottom 20%, then find the background color of that section in the bottom 20% and apply it to the tag until the user either scrolls to the next section, or scrolls up and another component takes over the background colour.
I have tried to use IntersectionObservor for this but I don't think it is the best approach for what I want.
Currently, my setup is, I am rendering multiple components after each other, each of them has a data attribute of "data-background={background}"
Then, the observer loops through, adds them all to the observer, and watches to find which one is on screen, but it isn't working completely for what I need.
Is there an easier way to achieve what I am looking for?
Here is the code I have so far
import Page from "../components/common/Page";
import Hero from "../components/molecules/Hero";
import TechStack from "#/components/organisms/TechStack";
import { useEffect } from "react";
const Home = () => {
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
console.log("entry", entry);
if (entry.isIntersecting) {
document.body.style.backgroundColor =
entry.target.dataset.background;
}
});
},
{ threshold: [0.20] }
);
// create an array of all the components to be watched
const components = [...document.querySelectorAll("[data-background]")];
components.forEach((component) => {
observer.observe(component);
});
}, []);
return (
<Page seo={{ title: "Starter Kit" }}>
<Hero />
<TechStack background="white"/>
<TechStack background="grey" />
<TechStack background="blue"/>
<TechStack background="green"/>
<TechStack background="grey"/>
<TechStack background="white"/>
</Page>
);
};
export default Home;

You can dynamically add the element to the observer when it mounted, like this
<div ref={(r) => r && observer.observe(r)} />
Here is the example: https://codesandbox.io/s/sleepy-margulis-1f7hz7

Related

React: fading loaded image in, but not the cached one

There are a lot of topics about animating the loaded image, but I haven't seen a great example of this in React yet. I came with this component myself:
import { useState } from 'react';
export default function FadingImage({ src, ...props }) {
const [loaded, setLoaded] = useState(false);
return (
<img
src={src}
onLoad={() => setLoaded(true)}
className={!loaded ? 'loading' : ''}
{...props}
/>
);
};
img {
transition: opacity .25s;
}
.loading {
opacity: 0;
}
It works fine in the beginning, but then it's annoying that the same images get faded-in every single time. I'd like the cached images to appear instantly.
In vanilla JS it just works, because it's all done in the same render cycle. I'm not sure how it can be achieved in React.
Package suggestions are appreciated, but I'd also like to know how to do it for educational purpose.

React: useState not preserving updated state upon click

Update: I was truly trying to reinvent the wheel here, the code below works if you use it in conjunction with React's Link or NavLink instead of anchor tags, it has built-in listening functionality that will keep track of the page you are currently on and pass along the updated state accordingly as your route changes to a different page.Thank you to everyone that chimed in and pointed me in the right direction!
I'm still fresh off the block with React, especially with hooks, but what I'm trying to accomplish is to trigger the 'active' tab class of my navbar elements through conditional rendering and managing state with useState.
However, when I call 'setActiveTabIdx' upon click, I can't tell if it's not updating state at all, or if it is and is resetting to the default value upon re-render. I was trying to use my dev tools to monitor upon click but it's happening too fast for me to say one way or the other. I've messed around a fair bit at this point trying a number of different things if anyone would be willing to take a look, thanks!
const NavBar = () => {
const [activeTabIdx, setActiveTabIdx] = useState(0)
const navItems = ['About', 'Services', 'Oils', 'New Patients', 'Courses', 'Recommendations', 'Contact' ]
const renderedItems = navItems.map((nav, idx) => {
const active = idx === activeTabIdx ? 'active' : ''
return (
<a
onClick={() => setActiveTabIdx(idx)}
href={`${nav.split(' ').join('').toLowerCase()}`}
className={`${active} item`}
key={idx}
>{nav}</a>
)
})
return (
<div className="ui tabular menu">
{renderedItems}
</div>
);
};
export default NavBar;
You are trying to change state (which is done) and then redirect user to other page (which is also done). The state resets after redirection.
It seems it is resetting, I added this block to check:
const NavBar = () => {
const [activeTabIdx, setActiveTabIdx] = useState(0)
const navItems = ['About', 'Services', 'Oils', 'New Patients', 'Courses', 'Recommendations', 'Contact' ]
// --------- Start block ---------
useEffect(() => {
console.log(`current state: ${activeTabIdx}`);
}, [activeTabIdx]);
// --------- End block ---------
const renderedItems = navItems.map((nav, idx) => {
const active = idx === activeTabIdx ? 'active' : ''
return (
<a
onClick={() => setActiveTabIdx(idx)}
href={`${nav.split(' ').join('').toLowerCase()}`}
className={`${active} item`}
key={idx}
>{nav}</a>
)
})
return (
<div className="ui tabular menu">
{renderedItems}
</div>
);
};
export default NavBar;
And to check codesandbox

Get the height (dimensions) of a Suspense element after loading in react

Basically I was trying to render a really really long list (potentially async) in React and I only want to render the visible entriesĀ±10 up and down.
I decided to get the height of the component that's holding the list, then calculate the overall list height/row height, as well as the scroll position to decide where the user have scrolled.
In the case below, SubWindow is a general component that could hold a list, or a picture, etc... Therefore, I decided it wasn't the best place for the calculations. Instead, I moved the calc to a different component and tried to use a ref instead
const BananaWindow = (props) => {
const contentRef = useRef(null)
const [contentRefHeight, setContentRefHeight] = useState(0)
useEffect(()=>setContentRefHeight(contentRef.current.offsetHeight), [contentRef])
//calc which entries to include
startIdx = ...
endIdx = ...
......
return (
<SubWindow
ref={contentRef}
title="all bananas"
content={
<AllBananas
data={props.data}
startIdx={startIdx}
endIdx={endIdx}
/>
}
/>
)
}
//this is a more general component. accepts a title and a content
const SubWindow = forwardRef((props, contentRef) => {
return (
<div className="listContainer">
<div className="title">
{props.title}
</div>
<div className="list" ref={contentRef}>
{props.content}
</div>
</div>
})
//content for all the bananas
const AllBanana = (props) => {
const [data, setData] = useState(null)
//data could be from props.data, but also could be a get request
if (props.data === null){
//DATA FETCHING
setData(fetch(props.addr).then()...)
}
return(
<Suspense fallback={<div>loading...</div>}>
//content
</Suspense>
}
PROBLEM: In BananaWindow, the useEffect is triggered only for initial mounting and painting. So I only ended up getting the offsetWidth of the placeholder. The useEffect does nothing when the content of SubWindow finishes loading.
UPDATE: Tried to use callback ref and it still only showed the height of the placeholder. Trying resize observer. But really hope there's a simpler/out of the box way for this...
So I solved it using ResizeObserver. I modified the hook from this repo to fit my project.

how to move focus to an item in react js?

I have implemented a list with infinite scroll in my demo application.on click of any row it will go to detail screen. It is working fine.
**I am facing a issue to focus the last selected row ** In other words
Run the application .it load first 20 items.Scroll to bottom to load more 20 items.
Then click any item let say 33rd row . it will display 33 in detail page.
Now click on back button it show focus on 0 or first row. I want to move focus to 33 row .or Move the scroll position to 33 position.
I use useContext api to store the items(all rows/data till last scroll) and selected item (selected index).
here is my code
https://codesandbox.io/s/dank-cdn-2osdg?file=/src/useInfinitescroll.js
import React from "react";
import { withRouter } from "react-router-dom";
import { useListState } from "./context";
function Detail({ location, history }) {
const state = useListState();
console.log(state);
return (
<div className="App">
<button
onClick={() => {
history.replace({
pathname: "/"
});
}}
>
back
</button>
<h2>{location.state.key}</h2>
<h1>detaiils</h1>
</div>
);
}
export default withRouter(Detail);
any update?
Use this before you redirect to the detailed page and store it in a state.
let position = document.documentElement.scrollTop
This will give your current position on the page. Once you are back to list view use
window.scrollTo(0, position)
to go back to where you were initially.
You could have a single source of truth for your element position. As an example I put the state of the position on the Router component since this seems to be the parent of the App component & Details.
const Router = () => {
const [previousClickedItemPos, setPreviousClickedItemPos] = useState(0);
/* import useLocation so that useEffect can be invoked on-route-change */
const location = useLocation();
/* basically pass this to the list items so you can update position on-click */
function handleClickList(element) {
setPreviousClickedItemPos(element);
}
useEffect(() => {
/* scroll to the element previously selected */
window.scrollTo(0, previousClickedItemPos);
}, [previousClickedItemPos, location]);
...
On your InfiniteList component:
<ListItem
onClick={e => {
dispatch({
type: "set",
payload: items
});
handleClickList(e.target.offsetTop); /* update position state */
goDetailScreen(item);
}}
key={item.key}
>
{item.value}
</ListItem>
CodeSandBox: https://codesandbox.io/s/modern-breeze-nwtln?file=/src/router.js

How to scroll to text input on focus when there is an input accessory with React Native

I'm working on a React Native app and many screens has forms with text input fields.
When I press the text input, the keyboard opens. I created a floating InputAccessory component which appears at the top of the keyboard to dismiss it, with the button "Done" on it.
However now that I have this accessory, when I click an input field or press the "Next" button on the keyboard to go to the next field, the ScrollView scrolls to align the bottom of the text input with the top of the keyboard. With this floating accessory it poses problems as you can see below you can't see the content of the text input because of this accessory, and I'd like to have the scrollview scrolling a bit more to display the entire text input.
I could probably do the calculation for this and run the .scrollTo() method from the ScrollView component but this pattern is very common to my entire app and I'm looking for an elegant solution that could be generic enough every single time I import a text input and focus on it.
Do you have any suggestion?
Thanks
I got the same issue before and i have 2 different solutions , Both of them worked for me.
1- Using react-native-keyboard-aware-scroll-view , Note that this library will already contain scrollView so you remove your own scroll view and use
<KeyboardAwareScrollView>
<View>
<TextInput />
</View>
</KeyboardAwareScrollView>
You can also check documentation for more info.
This solution is easier as you don't need to handle anything by yourself, but i think you will have some issues if you want to include scrollView inside it.
2- I once created a component AvoidKeyboard that actually does something similar to your solution, but it used to translate top the whole view with the keyboard height value, this solution worked perfectly also for me.
Implementation
import React, { Component } from 'react';
import { Animated, Easing, Keyboard } from 'react-native';
import PropTypes from 'prop-types';
class AvoidKeyboard extends Component {
constructor(props) {
super(props);
this.state = {
animatedViewHeight: new Animated.Value(0),
viewHeight: 0,
};
this.setViewHeightOnce = this.setViewHeightOnce.bind(this);
this.keyboardWillShow = this.keyboardWillShow.bind(this);
this.keyboardWillHide = this.keyboardWillHide.bind(this);
this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardDidShowListener && this.keyboardDidShowListener.remove();
this.keyboardDidHideListener && this.keyboardDidHideListener.remove();
}
setViewHeightOnce(event) {
const { height } = event.nativeEvent.layout;
if (this.state.viewHeight === 0) {
const avoidPaddingBottom = 15;
this.setState({
viewHeight: height + avoidPaddingBottom,
animatedViewHeight: new Animated.Value(height + avoidPaddingBottom),
});
}
}
keyboardWillShow(e) {
const { viewHeight } = this.state;
if (viewHeight) {
requestAnimationFrame(() => { // eslint-disable-line no-undef
Animated.timing(this.state.animatedViewHeight, {
toValue: (viewHeight - e.endCoordinates.height),
duration: 200,
delay: 0,
easing: Easing.inOut(Easing.ease),
}).start();
});
}
}
keyboardWillHide() {
requestAnimationFrame(() => { // eslint-disable-line no-undef
Animated.timing(this.state.animatedViewHeight, {
toValue: this.state.viewHeight,
duration: 200,
delay: 0,
easing: Easing.inOut(Easing.ease),
}).start();
});
}
render() {
let animatedHeight;
const { viewHeight } = this.state;
if (viewHeight > 0) {
animatedHeight = { maxHeight: this.state.animatedViewHeight };
}
return (
<Animated.View
style={[{ flex: 1, justifyContent: 'flex-end' }, animatedHeight]}
onLayout={this.setViewHeightOnce}
>
{this.props.children}
</Animated.View>
);
}
}
AvoidKeyboard.propTypes = {
children: PropTypes.node.isRequired,
};
export default AvoidKeyboard;
Now you just need to wrap your component or screen inside AvoidKeyboard and your screen height will shrink once keyboard is open, and you will be able to scroll the screen
I have had a lot of problems with keyboard in IOS. No KeyboardSpacer, react-native-keyboard-aware-scroll-view and more packages solved it.
Recently I discovered react-native-keyboard-manager and it solved all my problems without one line of code, also in modals and more (I don't have nothing to do with the author, but this package saved me the day). Give it a change.
I found a solution which doesn't involve hacky animation change.
When the keyboard opens, what I decided to do is to add some margin at the bottom of the ScrollView which correspond to the height of the InputAccessory. I then remove this margin when the keyboard closes. It looks like something like this:
import KeyboardListener from 'react-native-keyboard-listener';
...
render() [
<ScrollView
key={1}
style={{ marginBottom: this.state.scrollViewMarginBottom }}
/>,
<InputAccessory key={2} onLayout={...} />,
<KeyboardListener
key={3}
onWillShow={() => this.setState({ scrollViewMarginBottom: inputAccessoryHeight });
onWillHide={() => this.setState({ scrollViewMarginBottom: 0 })
/>
]
I was facing the same issue and reading online I figured out the following solution
For Android
Go to your AndroidManifest.xml and add android:windowSoftInputMode="adjustPan"
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustPan">
.....
</activity>
For IOS
Just follow the instructions in this repo.
https://github.com/douglasjunior/react-native-keyboard-manager.
Hope this helps. :)

Categories

Resources