I am working on showing images in a card using React. The images come from a API and are resolved when rendering the images page. Now I want to use the Dimmer component from the React Semantic-UI design library and dim an image on a mouseover. I tried the following example from the document page:
const dimmedID = this.state.dimmedID
const collections = this.state.collections
const collectionsList = collections.map((project) =>
<Dimmer.Dimmable
key = { project.id }
as = {Image}
dimmed = {dimmedID === project.id ? true : false}
dimmer = {{ active, content }}
onMouseEnter = {() => { this.handleShow(project.id) }}
onMouseLeave = {() => { this.handleShow('') }}
src = { project.thumbnail }
/>
)
When triggering the onMouseEnter, the dimmedID state object is set to the id of the image. However, all images that are rendered are being dimmed. Not the image which the mouse is on. I tried with a shorthand if-else on the dimmed parameter but that does not seem to work. When hovering with the mouse over one image, all images get dimmed.
Does someone know how to fix this?
Ok so apparently the fix is easy... so much for reading...
const dimmedID = this.state.dimmedID
const collections = this.state.collections
const collectionsList = collections.map((project) =>
<Dimmer.Dimmable
key = { project.id }
as = {Image}
dimmed = {dimmedID === project.id ? true : false}
dimmer = {{ active: dimmedID === project.id, content }}
onMouseEnter = {() => { this.handleShow(project.id) }}
onMouseLeave = {() => { this.handleShow('') }}
src = { project.thumbnail }
/>
)
Related
In a functional component, I have a page where I load an initial video, which is supposed to be a background welcome video to the page, and that video shouldn't have controls on it. However, once the user selected a new video, I want the controls to change. I'm using #u-wave/react-vimeo to display my video. I was thinking that the controls should just update whenever the state changes, but that isn't working for me. How should I hide videos when I want to, and show them when I want to?
My code is as follows:
const defaultVimeoId = '1234567890';
const VideoDisplayer = () => {
const {videoId} = useContext(VideoContext);
const [video, setVideo] = useState();
const [vimeoId, setVimeoId] = useState(defaultVimeoId);
const [isDefault, setIsDefault] = useState(true);
const [showControls, setShowControls] = useState(false);
useEffect(() => {
if (videoId) {
setShowControls(true);
setVimeoId(/*Does some magic here to get the vimeo ID of other videos*/);
setIsDefault(false);
}
else {
setIsDefault(true);
}
}, [videoId, isDefault, showControls]);
return (
<div>
<div>
<Vimeo
video={`https://vimeo.com/${vimeoId}`}
autoplay={isDefault ? true : false}
loop={isDefault ? true : false}
muted={isDefault ? true : false}
background={false}
controls={showControls}
style={{width: '100%'}}
responsive
/>
</div>
</div>
);
};
export default VideoDisplayer;
Whenever I click on a video and the context of the vimeo ID changes, the new video loads, but it won't let me change the control visibility this way. Any ideas here?
I'm coding a tab navigation system with a sliding animation, the tabs are all visible, but only the selected tab is scrolled to. Problem is that, I need to get the ref of the current selected page, so I can set the overall height of the slide, because that page may be taller or shorter than other tabs.
import React, { MutableRefObject } from 'react';
import Props from './Props';
import styles from './Tabs.module.scss';
export default function Tabs(props: Props) {
const [currTab, setCurrTab] = React.useState(0);
const [tabsWidth, setTabsWidth] = React.useState(0);
const [currentTabHeight, setCurrentTabHeight] = React.useState(0);
const [currentTabElement, setCurrentTabElement] = React.useState<Element | null>(null);
const thisRef = React.useRef<HTMLDivElement>(null);
let currentTabRef = React.useRef<HTMLDivElement>(null);
let refList: MutableRefObject<HTMLDivElement>[] = [];
const calculateSizeData = () => {
if (thisRef.current && tabsWidth !== thisRef.current.offsetWidth) {
setTabsWidth(() => thisRef.current.clientWidth);
}
if (currentTabRef.current && currentTabHeight !== currentTabRef.current.offsetHeight) {
setCurrentTabHeight(() => currentTabRef.current.offsetHeight);
}
}
React.useEffect(() => {
calculateSizeData();
const resizeListener = new ResizeObserver(() => {
calculateSizeData();
});
resizeListener.observe(thisRef.current);
return () => {
resizeListener.disconnect();
}
}, []);
refList.length = 0;
return (
<div ref={thisRef} className={styles._}>
<div className={styles.tabs}>
{ props.tabs.map((tab, index) => {
return (
<button onClick={() => {
setCurrTab(index);
calculateSizeData();
}} className={currTab === index ? styles.tabsButtonActive : ''} key={`nav-${index}`}>
{ tab.label }
<svg>
<rect rx={2} width={'100%'} height={3} />
</svg>
</button>
)
}) }
</div>
<div style={{
height: currentTabHeight + 'px',
}} className={styles.content}>
<div style={{
right: `-${currTab * tabsWidth}px`,
}} className={styles.contentStream}>
{ [ ...props.tabs ].reverse().map((tab, index) => {
const ref = React.useRef<HTMLDivElement>(null);
refList.push(ref);
return (
<div ref={ref} style={{
width: tabsWidth + 'px',
}} key={`body-${index}`}>
{ tab.body }
</div>
);
}) }
</div>
</div>
</div>
);
}
This seems like a reasonable tab implementation for a beginner. It appears you're passing in content for the tabs via a prop named tabs and then keeping track of the active tab via useState() which is fair.
Without looking at the browser console, I believe that React doesn't like the way you are creating the array of refs. Reference semantics are pretty challenging, even for seasoned developers, so you shouldn't beat yourself up over this.
I found a good article that discusses how to keep track of refs to an array of elements, which I suggest you read.
Furthermore, I'll explain the differences between that article and your code. Your issues begin when you write let refList: MutableRefObject<HTMLDivElement>[] = []; According to the React hooks reference, ref objects created by React.useRef() are simply plain JavaScript objects that are persisted for the lifetime of the component. So what happens when we have an array of refs like you do here? Well actually, the contents of the array are irrelevant--it could be an array of strings for all we care. Because refList is not a ref object, it gets regenerated for every render.
What you want to do is write let refList = React.useRef([]), per the article, and then populate refList.current with refs to your child tabs as the article describes. Referring back to the React hooks reference, the object created by useRef() is a plain JavaScript object, and you can assign anything to current--not just DOM elements.
In summary, you want to create a ref of an array of refs, not an array of refs. Repeat that last sentence until it makes sense.
I have an element at the top of my page that I want to be collapsible. The trouble is that if there are enough elements below it on the page (about 2000 or more), the act of collapsing/expanding causes the mouse to freeze for a few seconds. How can I add a collapsible element like this and still have a responsive UI?
My methods for collapsing that I have tried are rendering the collapsed element as "null" and rendering with height = 0. Both are slow.
The number of elements following the collapsible element in the example is not that big ~5000 - basically a table with a few hundred rows.
Code sandbox example here: https://codesandbox.io/s/2zi2s
I don't know if this can help. But on my work we implemented a component that can be collapsible with useLayoutEffect.
const InnerCardWrapper: React.FC<IInnerCardWrapper> = ({ isOpen, wrapperCssClasses = '', innerCssClasses = '', children }) => {
const innerCardHeightMeasures = useRef(0);
const [innerCardHeight, setInnerCardHeight] = useState(0);
const elementId = uuid();
useLayoutEffect(() => {
const cardInnerContainer = document.getElementById(elementId);
if (cardInnerContainer) {
innerCardHeightMeasures.current = cardInnerContainer.clientHeight;
}
if (innerCardHeightMeasures.current > 0) {
setInnerCardHeight(innerCardHeightMeasures.current);
}
}, [isOpen]);
useEffect(() => {
setInnerCardHeight(innerCardHeight === 0 ? innerCardHeightMeasures.current : 0);
}, [isOpen]);
return (
<div
style={{ height: innerCardHeight }}
className={`overflow-hidden transition-all ${isOpen ? 'border-b border-gray-light mt-6' : ''} ${wrapperCssClasses}`}
>
<div id={elementId} className={`py-3 ${innerCssClasses}`}>
{children}
</div>
</div>
);
};
export default InnerCardWrapper;
We use TailwindCSS you can check the CSS equivalent here.
Hope this works, please let me know.
I need to store scroll position on state. I am doing that in the following way. But don't know how it's not scrolling the div.
const [scroll, setScroll] = (0)
const scrollRef = useRef(null);
const onScroll = (e) => {
e.preventDefault();
setScroll(myRef.current.scrollLeft);
};
<div ref={myRef} onScroll={onScroll}
>
<h5>item 1 </h5>
<h5>item 2 </h5>
<h5>item 3 </h5>
</div>
You are calling e.preventDefault. This means that the default behaviour is prevented. In this case, the scroll.
Remove that line and it should work.
This can be used
componentDidMount() {
let left_scroll = document.querySelector('.className')
left_scroll.addEventListener('scroll', this.handleScroll);
}
handleScroll = (e) => {
let left_scroll = document.querySelector('.className')
if (left_scroll.scrollTop == 0){
document.getElementById('button_className').classList.remove("hidden")
}else if(left_scroll.scrollHeight > 800 && left_scroll.scrollTop >= 700){
document.getElementById('button_className').classList.add("hidden")
}
}
what I have done here is, get that div and I have tried to show and hide a button as per page scroll heigh
I have a problem with useState. editorDataOpen is updating correctly when set from openEditorData , but not when set from closeEditorData. The line console.log('Entering Profile > closeEditorData()') is reached without a problem.
The output I see in the console log is:
Render
Render
false (editorDataOpen set for the first time)
Render
Render
Render
Render
true (editorDataOpen set to true by a click, which opens Editor)
Entering Profile > closeEditorData() (triggered by a different click to close Editor, should set editorDataOpen to false, but doesn't)
Render
Render
and that's it, it never prints a last false, which would mean editorDataOpen is never set?
I've spent too much time on this today and I just cannot see where the bug is. Can someone help please? This is the code in question:
import React from 'react'
import {withTheme} from 'styled-components'
import {withContext} from '../../context/ContextHOC'
import withLoadingScreen from '../hocs/LoadingScreenHOC'
import {getEditorWidth} from '../../functions/funcEditor'
import {saveImagePlus} from '../../functions/funcDataSaver'
import Submodule from '../ui/Submodule'
import ImageCapsule from '../ui/ImageCapsule'
import EditorImage from '../editors/EditorImage'
import EditorProfileData from '../editors/EditorProfileData'
import Spacer from '../ui/Spacer'
import Table from '../ui/Table'
import ContainerGrid from '../ui/ContainerGrid'
import * as ops from '../../functions/funcStringMath'
import * as parse from '../../functions/funcDataParser'
const Profile = (props) => {
const s = props.theme.sizes
const c = props.theme.cards
const {setLoadingOn, setLoadingOff} = props
const [image, setImage] = React.useState(props.context.current.modules.me.profile.image)
const [editorImageOpen, setEditorImageOpen] = React.useState(false)
const [editorDataOpen, setEditorDataOpen] = React.useState(false)
const openEditorImage = () => setEditorImageOpen(true)
const openEditorData = () => setEditorDataOpen(true)
const closeEditorImage = () => {
setEditorImageOpen(false)
setLoadingOff()
}
const closeEditorData = () => {
console.log('Entering Profile > closeEditorData()')
setEditorDataOpen(false)
setLoadingOff()
}
React.useEffect(() => console.log(editorDataOpen), [editorDataOpen])
const updateAfterSavingImage = (img) => {
setImage({
url: img.url,
scale: img.scale,
position: img.position
})
closeEditorImage()
}
const handleImageChanged = (img) => {
if (img != undefined){
setLoadingOn()
const data = {
companyId: props.context.current.company.id,
userId: props.context.current.user.id,
routeFile: props.context.routes.meProfileImage,
routeData: props.context.routes.meProfileImageData,
}
saveImagePlus(img, data, updateAfterSavingImage)
}
else {
console.log('Error: Image received is undefined, cannot save.')
closeEditorImage()
}
}
const spacer =
<Spacer
width = '100%'
height = {s.spacing.default}
/>
const unparsedData = props.context.current.modules.me.profile.data
const parsedData = parse.profileData(props.context.current.modules.me.profile.data)
console.log('Render')
return(
<Submodule
isMobile = {c.cardsPerRow == 1 ? true : false}
header = {{
text: 'Profile',
}}
{...props}
>
<ImageCapsule
onClick = {openEditorImage}
id = {'container_imageprofile'}
width = '100%'
image = {image}
$nodrag
/>
{editorImageOpen &&
<EditorImage
open = {editorImageOpen}
closeSaving = {handleImageChanged}
closeWithoutSaving = {closeEditorImage}
image = {image}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Image',
}}
/>
}
{spacer}
{spacer}
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
onClick = {openEditorData}
$clickable
>
<Table
$nomouse
width = '100%'
data = {parsedData}
settings = {{
cell: {
padleft: s.spacing.default,
padright: s.spacing.default,
padtop: ops.round((ops.divide([s.spacing.default, 4]))),
padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
},
columns: [
{type: 'defaultRight', width: '30%'},
{type: 'default', width: '70%'},
]
}}
/>
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
</ContainerGrid>
{spacer}
{spacer}
</Submodule>
)
}
export default (
withTheme(
withContext(
withLoadingScreen(
Profile
)
)
)
)
EDIT: This one has been solved by Dehan de Croos, many thanks!
So as Dehan mentions below, the event was bubbling up and triggering openEditorData in ContainerGrid. The whole thing was mistifying because editorImageOpen was working correctly while editorDataOpen was not, and they both do the same thing: open an Editor window.
Once Dehan solved the mistery, I realized that the differene between the 2 is that inside ImageCapsule there is a ClickLayer component, which is there just to catch the click and the callback.
I did not use a ClickLayer with ContainerGrid, and that is why the event was able to bubble up.
Following Dehan's advice, I solved just by adding a ClickLayer inside ContainerGrid, like this:
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
// onClick = {openEditorData}
$clickable
>
<ClickLayer
onClick = {openEditorData}
/>
<Table
$nomouse
width = '100%'
data = {parsedData}
settings = {{
cell: {
padleft: s.spacing.default,
padright: s.spacing.default,
padtop: ops.round((ops.divide([s.spacing.default, 4]))),
padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
},
columns: [
{type: 'defaultRight', width: '30%'},
{type: 'default', width: '70%'},
]
}}
/>
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
</ContainerGrid>
It would be better to have a working code snippet to diagnose this, but there is a good chance that the event might be bubbling up the component tree and triggering the openEditorData event here.
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
onClick = {openEditorData}
$clickable
>
To check this quickly move the component shown below outside the <ContainerGrid ... /> component. And check whether this fixes things.
To clarify here, The move of code should happen so that <
EditorProfileData ... /> will never appear as a child component of
<ContainerGrid ... />
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
If now it's working properly and you desperately need to maintain the above component hierarchy/structure. You can call
stopPropegation but you will need to have the native JS event with you. To explain how to do this I need to know what <EditorProfileData ... /> looks like. But assuming the close prop does return the native click event as a callback, the fix will look something like this.
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
//close = {closeEditorData}
// If close provides the native event use following
close = { e => {
e.stopPropagation();
closeEditorData();
}}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
If not we need to find the original onClick event and pass up to that prop callback.