I'm trying to add a class to an element using Material UI in a scroll event like this.
const useStyles = makeStyles({
sticky: {
position: 'fixed',
top: 0,
width: '100%',
}
});
export default function myBar() {
React.useEffect(() => {
const myBar = document.getElementById("myBar");
const sticky = myBar.offsetTop;
const scrolling = window.addEventListener("scroll", () => {
if (window.pageYOffset > sticky) {
myBar.classList.add("sticky");
} else {
myBar.classList.remove("sticky");
}
});
return () => {
window.removeEventListener("scroll", scrolling);
};
}, []);
const classes = useStyles();
return (
<header id="myBar">
// some content
</header>
);
};
The problem is that Mterial Ui will generate some random numbers after class name, like sticky_123 so it will never be only sticky
Is it any way I can solve this problem?
The problem is that Material Ui will generate some random numbers after class name, like sticky_123 so it will never be only sticky
In order to use the className generated by Material UI, you must use classes.sticky instead of "sticky".
By the way, the component name should be in PascalCase.
const useStyles = makeStyles({
sticky: {
position: 'fixed',
top: 0,
width: '100%',
}
});
export default function MyBar() {
const classes = useStyles();
useEffect(() => {
const myBar = document.getElementById("myBar");
const sticky = myBar.offsetTop;
const scrollHandler = () => {
if (window.pageYOffset > sticky) {
myBar.classList.add(classes.sticky);
} else {
myBar.classList.remove(classes.sticky);
}
};
window.addEventListener("scroll", scrollHandler)
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, [classes]);
return (
<header id="myBar">
// some content
</header>
);
};
Related
I had never used React Class Components And Want to shoot confetti when some event happened. I'm confused with Class Component. Hope You can help with this issue. tried by creating arrow functions and removing this keyword .But I can't understand how to transform getInstance() function even why it is there?
import React, { Component } from 'react';
import ReactCanvasConfetti from 'react-canvas-confetti';
export default class Confetti extends Component {
getInstance = (instance) => {
// saving the instance to an internal property
this.confetti = instance;
}
onClickDefault = () => {
// starting the animation
this.confetti();
}
onClickCustom = () => {
// starting the animation with custom settings
this.confetti({ particleCount: Math.ceil(Math.random() * 1000), spread: 180 });
}
onClickCallback = () => {
// calling console.log after the animation ends
this.confetti().then(() => {
console.log('do something after animation');
});
}
onClickReset = () => {
// cleaning the canvas
this.confetti.reset();
}
render() {
const style = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: -1
};
const stylediv = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: 300000
};
return (
<>
<div style={stylediv}>
<ReactCanvasConfetti
// set the styles as for a usual react component
style={style}
// set the class name as for a usual react component
className={'yourClassName'}
// set the callback for getting instance. The callback will be called after initialization ReactCanvasConfetti component
refConfetti={this.getInstance}
/>
<button onClick={this.onClickDefault}>Fire with default</button>
<button onClick={this.onClickCustom}>Fire with custom</button>
<button onClick={this.onClickCallback}>Fire with callback</button>
<button onClick={this.onClickReset}>Reset</button>
</div>
</>
);
}
}
I'm trying to create Functional Component of the above Class Component
import React, { useRef } from 'react';
import ReactCanvasConfetti from 'react-canvas-confetti';
const Confetti = () => {
const Ref = useRef()
const getInstance = (instance) => {
if (Ref.current) {
Ref.current.confetti = instance
}
}
const onClickDefault = () => {
Ref.current.confetti();
}
const onClickCustom = () => {
Ref.current.confetti({ particleCount: Math.ceil(Math.random() * 1000),
spread: 180 });
}
const onClickCallback = () => {
Ref.current.confetti().then(() => {
console.log('do something after animation');
});
}
const onClickReset = () => {
Ref.current.confetti.reset();
}
const style = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: -1
};
const stylediv = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: 300000
};
return (
<>
<div ref={Ref} style={stylediv}>
<ReactCanvasConfetti
style={style}
className={'yourClassName'}
refConfetti={getInstance}
/>
<button onClick={onClickDefault}>Fire with default</button>
<button onClick={onClickCustom}>Fire with custom</button>
<button onClick={onClickCallback}>Fire with callback</button>
<button onClick={onClickReset}>Reset</button>
</div>
</>
);
}
export default Confetti
height and width of a div container in react when I am resizing it using CSS resize property
I have tried this but i am getting error
import React, { useState, useRef, useEffect } from 'react';
const TextAreaWrapper = () => {
const [dimensions, setDimensions] = useState({ height: 0, width: 0 });
const textareaRef = useRef(null);
useEffect(() => {
const textarea = textareaRef.current;
const handleResize = () => {
console.log('Hello world');
setDimensions({
height: textarea.offsetHeight,
width: textarea.offsetWidth,
});
};
textarea.addEventListener('resize', handleResize);
return () => {
textarea.removeEventListener('resize', handleResize);
};
}, []);
return (
<div>
<textarea ref={textareaRef} />
<p>Height: {dimensions.height}</p>
<p>Width: {dimensions.width}</p>
</div>
);
};
export default TextAreaWrapper;
Try this (check that the ref is not null):
useEffect(() => {
const textarea = textareaRef.current;
if (!textarea) {
return;
}
const handleResize = () => {
setDimensions({
height: textarea.offsetHeight,
width: textarea.offsetWidth,
});
};
textarea.addEventListener('resize', handleResize);
return () => {
textarea.removeEventListener('resize', handleResize);
};
}, [textareaRef.current]);
I have an audio element that is being used in multiple other components to play songs. I would like to integrate wavesurfer to this already existing audio. This issue https://github.com/katspaugh/wavesurfer.js/issues/986 says I can load the audio tag, but I get errors when doing it. I believe it's because my audio component is actually of type ForwardRefExoticComponent<Props & RefAttributes<HTMLAudioElement>> instead of just an HTMLAudioElement
const WavesurferComponent = (props: Props) => {
const { isPlaying } = props;
const [waver, setWaver] = useState<MODWaveSurfer | null>(null);
const waveformRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!waveformRef.current) return;
const modedWaveSurfer = WaveSurfer as unknown as MODWaveSurfer;
const wavesurfer = modedWaveSurfer.create({
barWidth: 3,
container: waveformRef.current,
backend: 'WebAudio',
height: 30,
barRadius: 3,
responsive: true,
progressColor: ['#26D1A8', '#AC4EFD', '#F1419E', '#FED503', '#FE5540'],
waveColor: '#1C1B1B',
barGap: 3,
cursorColor: 'transparent',
});
wavesurfer.load(AudioElement);
setWaver(wavesurfer);
return () => wavesurfer.destroy();
}, []);
useEffect(() => {
if (isPlaying && waver) waver.playPause();
}, [isPlaying]);
return (
<div>
<div ref={waveformRef} style={{ width: '225px', margin: '0 auto' }} />
</div>
);
};
// AUDIO ELEMENT
export const AudioElement = forwardRef<HTMLAudioElement, Props>((props, ref) => {
const { className } = props;
return <audio ref={ref} className={className} />;
});
*Trying to show a chat history with infinite reload similar to Skype or any popular chat app
In a chat page. If my chat messages limit is 10 messages.
And the chat has 30.
It will show latest 10 when I load the chat.
When I scroll to the top I want to see the previous 10.
I tried this myself and the scroll position stays the same but the messages load in the view.
It should load to the top and hold the scroll position.
How can this be done?
Here's my page:
https://pastebin.com/36xZPG1W
import React, { useRef, useState, useEffect } from 'react';
import produce from 'immer';
import dayjs from 'dayjs';
import { WithT } from 'i18next';
import * as ErrorHandler from 'components/ErrorHandler';
import useOnScreen from 'utils/useOnScreen';
import getLang from 'utils/getLang';
import Message from './Message';
const limit = 10;
const lang = getLang();
interface IMessagesProps extends WithT {
messages: any;
currentUserID: string;
chatID: string;
fetchMore: any;
typingText: any;
setSelectedMsg: any;
removeMessage: any;
}
const Messages: React.FC<IMessagesProps> = ({
messages,
currentUserID,
chatID,
fetchMore,
setSelectedMsg,
removeMessage,
t,
}) => {
const elementRef = useRef(null);
const isOnScreen = useOnScreen(elementRef);
const topElementRef = useRef(null);
const topIsOnScreen = useOnScreen(topElementRef);
const isUserInside = useRef(true);
const scroller = useRef<HTMLDivElement>(null);
const messagesEnd = useRef<HTMLDivElement>(null);
const [hasMore, setHasMore] = useState(true);
useEffect(() => {
scrollToBottom();
}, []);
useEffect(() => {
autoscroll();
}, [messages]);
//NOT WORKING
const autoscroll = () => {
// Visible height
const visibleHeight = scroller.current.offsetHeight;
// Height of messages container
const containerHeight = scroller.current.scrollHeight;
// How far have I scrolled?
const scrollOffset = scroller.current.scrollTop + visibleHeight;
// New message element
const firstChild = scroller.current.firstElementChild;
console.log(`visibleHeight`, visibleHeight);
console.log(`containerHeight`, containerHeight);
console.log(`scrollOffset`, scrollOffset);
console.log(`firstChild`, firstChild.offsetHeight);
console.log(`firstChild`, firstChild.scrollHeight);
console.log(`firstChild`, firstChild);
scroller.current.scrollTop = scrollOffset;
// // Height of the new message
// const newMessageStyles = getComputedStyle($newMessage)
// const newMessageMargin = parseInt(newMessageStyles.marginBottom)
// const newMessageHeight = $newMessage.offsetHeight + newMessageMargin
// // Visible height
// const visibleHeight = $messages.offsetHeight
// // Height of messages container
// const containerHeight = $messages.scrollHeight
// // How far have I scrolled?
// const scrollOffset = $messages.scrollTop + visibleHeight
// if (containerHeight - newMessageHeight <= scrollOffset) {
// $messages.scrollTop = $messages.scrollHeight
// }
};
const fetchDataForScrollUp = cursor => {
ErrorHandler.setBreadcrumb('fetch more messages');
if (!hasMore) {
return;
}
fetchMore({
variables: {
chatID,
limit,
cursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult?.getMessages || fetchMoreResult.getMessages.messages.length < limit) {
setHasMore(false);
return previousResult;
}
const newData = produce(previousResult, draftState => {
draftState.getMessages.messages = [...previousResult.getMessages.messages, ...fetchMoreResult.getMessages.messages];
});
return newData;
},
});
};
if (messages?.length >= limit) {
if (topIsOnScreen) {
fetchDataForScrollUp(messages[messages.length - 1].id);
}
}
if (isOnScreen) {
isUserInside.current = true;
} else {
isUserInside.current = false;
}
const scrollToBottom = () => {
if (messagesEnd.current) {
messagesEnd.current.scrollIntoView({ behavior: 'smooth' });
}
};
const groupBy = function (arr, criteria) {
return arr.reduce(function (obj, item) {
// Check if the criteria is a function to run on the item or a property of it
const key = typeof criteria === 'function' ? criteria(item) : item[criteria];
// If the key doesn't exist yet, create it
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
obj[key] = [];
}
// Push the value to the object
obj[key].push(item);
// Return the object to the next item in the loop
return obj;
}, {});
};
const objectMap = object => {
return Object.keys(object).reduce(function (result, key) {
result.push({ date: key, messages: object[key] });
return result;
}, []);
};
const group = groupBy(messages, datum => dayjs(datum.createdAt).locale(lang).format('dddd, MMMM D, YYYY').toLocaleUpperCase());
const messageElements = objectMap(group)
.reverse()
.map((item, index) => {
const messageElements = item.messages
.map(message => {
return (
<Message
key={uniqueKey}
message={message}
currentUserID={currentUserID}
lang={lang}
removeMessage={removeMessage}
t={t}
chatID={chatID}
setSelectedMsg={setSelectedMsg}
/>
);
})
.reverse();
return messageElements;
})
.reduce((a, b) => a.concat(b), []);
return (
<div style={{ marginBottom: '25px' }}>
<div ref={topElementRef} />
<div
style={{
position: 'relative',
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
height: '100%',
overflow: 'hidden',
}}
ref={scroller}
>
{messageElements}
<div ref={elementRef} style={{ position: 'absolute', bottom: '5%' }} />
</div>
</div>
);
};
export default Messages;
Been stuck on this for 2 weeks lol. Any advice is helpful :)
Have you tried scrollIntoView ? you can try after changing your autoscroll function like following
const autoscroll = () => {
elementRef.current.scrollIntoView({ behavior: 'smooth' })
};
I know many ways to do this without JSS, but this paradigm seems to make it very difficult:
const Backdrop = () => {
const {height, width} = useWindowSize()
const css = makeStyles(() => createStyles({
root : {
backgroundColor: 'black',
backgroundImage: 'url(/img/bg.jpg)',
height, // this value should update when window size changes
width,
position:'fixed',
}
}))()
return <div className={css.root}>
</div>
}
Update:
I copy-pasted myself into a way to make this work, but it's just too much code compared to traditional CSS
const [_windowSize, $_windowSize] = useState({ width: window.innerWidth, height: window.innerHeight })
const handler = () => $_windowSize({ width: window.innerWidth, height: window.innerHeight })
useEffect(() => {
window.addEventListener("resize", handler)
return () => window.removeEventListener("resize", handler)
}, [])
return _windowSize
}
const Backdrop = () => {
useWindowSize()
const css = makeStyles(() => createStyles({
root : {
backgroundColor: 'black',
backgroundImage: 'url(/img/bg.jpg)',
backgroundSize: 'cover',
height: () => window.innerHeight,
width: () => window.innerWidth,
position:'fixed',
}
}))()
return <div className={css.root}></div>
}
Somehow makeStyles knows of every update in the DOM. It's really unintuitive.
Might this suit you're need?
const { height, width } = useWindowDimensions();
const style = {
root : {
backgroundColor: 'black',
backgroundImage: 'url(/img/bg.jpg)',
backgroundSize: 'cover',
height: height,
width: width,
position:'fixed'
}
}
return <div style={style.root}></div>
useWindowDimensions is a custom effect defined like this:
/**
* Returns window dimensions, listening to resize event.
*
* Example:
*
* const Component = () => {
* const { height, width } = useWindowDimensions();
* }
*/
export function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(
getWindowDimensions()
);
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowDimensions;
}
the effect listen to window resize and update the {width, height} state causing a clean rerendering.