Centering active element inside custom React carousel - javascript

I have a simple custom React carousel:
https://codesandbox.io/s/infallible-wood-740s2?fontsize=14
It goes through numbers from 1 to 100. I'm trying to have an active number centered with large font size, and its neighbors with smaller font size:
To progress through this carousel, you have to click on the carousel.
The issue is that progressing through the carousel numbers starts to misalign. I suppose that it is because of the wrong calculation for left property.
I've tried to tweak the formula, use React Transition Group, and find some existing package which could solve this issue, but I haven't succeeded. I would appreciate any help.
Component code:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Wrapper,
NumbersWrapper,
NumbersScroller,
NumberText
} from "./Numbers.style";
const hundred = new Array(100)
.fill(0)
.map((k, v) => ({ key: v, label: v + 1 }));
class Numbers extends Component {
constructor(props) {
super(props);
this.state = {
activeNumber: 0
};
}
setActiveNumber(number) {
this.setState({
activeNumber: number
});
}
render() {
const { activeNumber } = this.state;
const intAciveNumber = Number(activeNumber);
return (
<Wrapper>
<NumbersWrapper
onClick={() => this.setActiveNumber(intAciveNumber + 1)}
>
<NumbersScroller
style={{
left: `${130 - intAciveNumber * 55}px`
}}
>
{hundred.map(({ key, label }) => {
const isNeighbor =
key + 1 === activeNumber || key - 1 === activeNumber;
const isActive = key === activeNumber;
return (
<NumberText
key={key}
isNeighbor={isNeighbor}
isActive={isActive}
>
{label}
</NumberText>
);
})}
</NumbersScroller>
</NumbersWrapper>
</Wrapper>
);
}
}
export default Numbers;
const rootElement = document.getElementById("root");
ReactDOM.render(<Numbers />, rootElement);
Numbers.style.js:
import styled from "styled-components";
export const Wrapper = styled.div`
height: 549px;
width: 612px;
border-radius: 28px;
background-color: #ffffff;
margin-top: 116px;
padding: 26px 0 0 0;
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
left: 50%;
top: 0%;
transform: translate(-50%, -10%);
overflow: hidden;
`;
export const NumbersWrapper = styled.div`
white-space: nowrap;
width: 359.5px;
overflow: hidden;
`;
export const NumbersScroller = styled.div`
transition: all 150ms ease-in;
position: relative;
left: 130px;
`;
const numberTextStyle = props => {
if (props.isNeighbor) {
return `
height: 88px;
width: 53px;
opacity: 0.45;
color: #6C879C;
font-size: 80px;
font-weight: 300;
letter-spacing: -1.6px;
line-height: 88px;
text-align: center;
`;
}
if (props.isActive) {
return `
height: 156px;
color: #6C879C;
font-size: 150px;
font-weight: 300;
letter-spacing: -3px;
line-height: 156px;
text-align: center;
`;
}
return `
opacity: 0.2;
color: #6C879C;
font-size: 40px;
font-weight: 300;
letter-spacing: -0.8px;
line-height: 48px;
text-align: center;
`;
};
export const NumberText = styled.span`
font-family: Avenir;
margin: 0 15px;
user-select: none;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
${props => numberTextStyle(props)}
`;

Related

how to make a vertical slide of text in React

I am trying to make a vertical text slide. Having not found help in reactjs, I try to do it myself and I'm pretty happy with the result but a slight bug appears at the end of each word slide.
I imagine that there are several ways to create this animation but it is the only one that comes to me with my knowledge in JS.
Here my code:
import React, { useEffect, useRef, useState } from 'react'
const Version1 = () => {
const [ words, setWords ] = useState(['Victor', 'Alex', 'Lucie'])
const wrapperRef = useRef()
const handleAnim = () => {
setTimeout(() => {
const copyWords = [ ...words ];
const firstElem = copyWords.splice(1)
wrapperRef.current.style.transition = 'none';
wrapperRef.current.style.top = '0px'
setWords([ ...firstElem.concat(copyWords) ])
},1000);
wrapperRef.current.style.transition = '0.5s';
wrapperRef.current.style.top = '-70px'
}
useEffect(() => {
setTimeout(() => {
handleAnim()
}, 2000);
})
return (
<>
<div className="test-container">
<div className='test-title'>Hello</div>
<div className='text-container-word'>
<div ref={wrapperRef} className='text-container-word-wrapper'>
<span className='text-word'>{words[0]}</span>
<span className='text-word'>{words[1]}</span>
</div>
</div>
</div>
<style jsx>
{`
.test-container {
padding: 100px 0;
width: 100%;
display: flex;
}
.test-title {
font-size: 48px;
font-weight: bold;
color: blue;
}
.text-container-word {
position: relative;
width: 200px;
height: 70px;
background-color: green;
display: inline-block;
overflow: hidden;
margin-top: -7px;
}
.text-container-word-wrapper {
height: auto;
position: relative;
top: 0px;
}
.test-container h1 {
position: relative;
display: inline;
padding: 10px;
}
.text-word {
height: 70px;
font-size: 48px;
font-weight: bold;
display: block;
transition: 0.5s;
line-height: 70px;
}
`}
</style>
</>
)
}
export default Version1
Here is a pure css based solution that uses words from state without the useEffect hacks.
const {useState} = React;
function App() {
const [words, setWords] = useState(["Victor", "Alex", "Lucie", "Michael"]);
return (
<div className="App">
<div className="scroller">
<span>
{words[0]}
<br />
{words[1]}
<br />
{words[2]}
<br />
{words[3]}
</span>
</div>
</div>
);
}
ReactDOM.render(
<App/>,
document.getElementById("react")
);
.App {
font-family: sans-serif;
text-align: center;
}
.scroller {
height: 1.2em;
line-height: 1.2em;
position: relative;
overflow: hidden;
font-size: 40px;
text-align: center;
}
.scroller > span {
position: absolute;
top: 0;
animation: slide 6s infinite;
font-weight: bold;
background-color: green;
}
#keyframes slide {
0% {
top: 0;
}
25% {
top: -1.2em;
}
50% {
top: -2.4em;
}
75% {
top: -3.6em;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react">
set height and width with overflow-f:scroll
overflow-y: scroll;
you can also see this: https://www.w3schools.com/cssref/tryit.asp?filename=trycss3_overflow-y

How to display data from clicked container to other con

Can someone help me to display specific song while clicking on that in Your Playlist container on the left??
I am trying to list data on the left in Your Playlist container When I click on one of the music it should show it in Your Playlist container. It has to save it to browser history as well and it has to remove it from Search because it is already gonna be in Your Playlist container. I will deploy it later to Firebase but now I need help.
It should be added to the left while clicking on one of the listed songs after a search.
Please support me on that.
I am adding my codes here as well for my project:
I have App.js
import "./App.css";
import MySongs from "./MySongs.js";
import Search from "./Search.jsx";
function App() {
return (
<div className="App">
<div className="body">
<MySongs />
<Search />
</div>
</div>
);
}
export default App;
.App {
background-color: #303030;
width: 100%;
height: 100vh;
}
.body {
display: flex;
}
I have Search.jsx
import React, { useState, useEffect, useRef } from "react";
import "./Search.css";
import styled from 'styled-components';
import { IoSearch,IoClose } from "react-icons/io5";
import {motion, AnimatePresence} from "framer-motion";
import {useClickOutside} from "react-click-outside-hook";
import MoonLoader from 'react-spinners/MoonLoader';
import { useDebounce } from "./hooks/debounceHook";
import axios from "axios";
import { TvShow } from "./tvShow";
const SearchBarContainer = styled(motion.div)`
margin-left: 10px;
margin-top: 20px;
display: flex;
flex-direction: column;
width: 96%;
height: 2.5em;
background-color: #424242;
border-radius: 3px;
`;
const SearchInputContainer = styled.div`
width: 98%;
min-height: 2.5em;
display: flex;
align-items: center;
position: relative;
padding: 2px 15px;
`;
const SearchInput = styled.input`
width: 100%;
height: 100%;
outline: none;
border: none;
font-size: 15px;
color: white;
font-weight: 300;
border-radius: 6px;
background-color: transparent;
&:focus {
outline: none;
&::placeholder {
opacity: 0;
}
}
&::placeholder {
color: #white;
transition: all 250ms ease-in-out;
}
`;
const SearchIcon = styled.span`
color: #bebebe;
font-size: 14px;
margin-right: 10px;
margin-top: 6px;
vertical-align: middle;
`;
const CloseIcon = styled(motion.span)`
color: #bebebe;
font-size: 15px;
vertical-align: middle;
transition: all 200ms ease-in-out;
cursor: pointer;
&:hover {
color: #dfdfdf;
}
`;
const LineSeperator = styled.span`
display: flex;
min-width: 100%;
min-height: 2px;
background-color: #d8d8d878;
`;
const SearchContent = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 1em;
overflow-y: auto;
`;
const LoadingWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
const WarningMessage = styled.span`
color: #a1a1a1;
font-size: 14px;
display: flex;
align-self: center;
justify-self: center;
`;
const containerVariants = {
expanded: {
height: "26em",
},
collapsed: {
height: "2.5em",
},
};
const containerTransition = { type: "spring", damping: 22, stiffness: 150 };
export function SearchBar(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [tvShows, setTvShows] = useState([]);
const [noTvShows, setNoTvShows] = useState(false);
const isEmpty = !tvShows || tvShows.length === 0;
const changeHandler = (e) => {
e.preventDefault();
if (e.target.value.trim() === "") setNoTvShows(false);
setSearchQuery(e.target.value);
};
const expandContainer = () => {
setExpanded(true);
};
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoTvShows(false);
setTvShows([]);
if (inputRef.current) inputRef.current.value = "";
};
useEffect(() => {
if (isClickedOutside) collapseContainer();
}, [isClickedOutside]);
const searchTvShow = async () => {
if (!searchQuery || searchQuery.trim() === "") return;
setLoading(true);
setNoTvShows(false);
const options = {
method: 'GET',
url: 'https://deezerdevs-deezer.p.rapidapi.com/search',
params: {q: searchQuery},
headers: {
'x-rapidapi-host': 'deezerdevs-deezer.p.rapidapi.com',
'x-rapidapi-key': '6a99d5e101msh1e9f2b2f948746fp1ae1f3jsn6b458fe8b4e4'
}
};
axios.request(options).then(function (response) {
if (response) {
if (response.data && response.data.length === 0) setNoTvShows(true);
setTvShows(response.data.data);
}
}).catch(function (error) {
console.error(error);
});
setLoading(false);
};
useDebounce(searchQuery, 500, searchTvShow);
// console.log(tvShows);
return (
<div className="my__search">
<SearchBarContainer
animate={isExpanded ? "expanded" : "collapsed"}
variants={containerVariants}
transition={containerTransition}
ref={parentRef}
>
<SearchInputContainer>
<SearchIcon>
<IoSearch />
</SearchIcon>
<SearchInput
placeholder="Search for Series/Shows"
onFocus={expandContainer}
ref={inputRef}
value={searchQuery}
onChange={changeHandler}
/>
<AnimatePresence>
{isExpanded && (
<CloseIcon
key="close-icon"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={collapseContainer}
transition={{ duration: 0.2 }}
>
<IoClose />
</CloseIcon>
)}
</AnimatePresence>
</SearchInputContainer>
{isExpanded && <LineSeperator />}
{isExpanded && (
<SearchContent>
{isLoading && (
<LoadingWrapper>
<MoonLoader loading color="#000" size={20} />
</LoadingWrapper>
)}
{!isLoading && isEmpty && !noTvShows && (
<LoadingWrapper>
<WarningMessage>Start typing to Search</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && noTvShows && (
<LoadingWrapper>
<WarningMessage>No Tv Shows or Series found!</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && !isEmpty && (
<>
{tvShows.map((show) => (
<TvShow
key={show.id}
thumbnailSrc={show.album.cover_medium}
name={show.title_short}
artist={show.artist.name}
/>
))}
</>
)}
</SearchContent>
)}
</SearchBarContainer>
</div>
);
}
export default SearchBar;
.my__search {
margin-top: 20px;
flex: 0.6;
height: 450px;
border-radius: 5px;
border: 1px solid black;
margin-left: 80px;
background-color: #424242;
}
I have tvShow.jsx
import React, { useState } from "react";
import styled from "styled-components";
import {ImDownload} from "react-icons/im";
const TvShowContainer = styled.div`
width: 96%%;
min-height: 3em;
display: flex;
border-bottom: 2px solid #555555;
align-items: center;
`;
const Thumbnail = styled.div`
width: auto;
height: 80%;
display: flex;
flex: 0.4;
img {
border-radius: 20px;
width: auto;
height: 100%;
}
`;
const Name = styled.h3`
font-size: 12px;
color: white;
flex: 2;
display: flex;
flex-direction: column;
`;
const Artist = styled.span`
margin-top: 10px;
font-size: 8px;
color: white;
display: flex;
align-items: center;
`;
const Rating = styled.span`
color: #a1a1a1;
font-size: 16px;
display: flex;
flex: 0.2;
`;
export function TvShow(props) {
const { thumbnailSrc, name, artist,clickedMusic } = props;
const [wantedMusic, setWantedMusic] = useState("");
// const [clickedShow, setClickedShow] = useState("");
// function clickedContainer(e){
// const element = e.currentTarget();
// setClickedShow(element);
// console.log("I am clickedShow " +clickedShow);
// }
return (
<TvShowContainer onclick="location.href='#';" >
<Thumbnail>
<img src={thumbnailSrc} />
</Thumbnail>
<Name>{name}
<Artist>
{artist}
</Artist>
</Name>
</TvShowContainer>
);
}
I have mySongs.js
import React from "react";
import "./MySongs.css";
function MySongs() {
return (
<div className="my__songs">
<p>Your Playlist</p>
</div>
);
}
export default MySongs;
.my__songs {
margin-left: 10px;
margin-top: 20px;
flex: 0.3;
height: 300px;
height: 450px;
border: 1px solid black;
border-radius: 5px;
background-color: #424242;
}
.my__songs > p {
color: white;
opacity: 90%;
margin-left: 10px;
font-size: 13px;
}
Only partly answering the question: move an item from one list to another (and back) on mouse click.
The basic situation can be solved if you use the parent component to hold the state that the children components display. Then you only need to implement a function that toggles a "flag" (like selected), and the components can be rendered based on that flag.
const {useState} = React
const tracklist = [
{
id: 1,
title: 'Track 1',
selected: false,
},
{
id: 2,
title: 'Track 2',
selected: false,
},
{
id: 3,
title: 'Track 3',
selected: false,
},
{
id: 4,
title: 'Track 4',
selected: false,
},
{
id: 5,
title: 'Track 5',
selected: false,
},
]
const ListItem = ({ title, onToggleSelect }) => <div className="list-item" onClick={onToggleSelect}>{title}</div>
const App = ({ tracklist }) => {
const [tracks, setTracks] = useState(tracklist)
const toggleSelect = (id) => {
setTracks((prevState) => prevState.map(item => item.id === id ? {...item, selected: !item.selected} : item))
}
const listItem = (track) => <ListItem key={track.id} {...track} onToggleSelect={() => toggleSelect(track.id)}/>
return (
<div className="container" >
<div className="tracklist">
{
tracks
.filter(({ selected }) => selected)
.map(listItem)
}
</div>
<div className="tracklist">
{
tracks
.filter(({ selected }) => !selected)
.map(listItem)
}
</div>
</div>
)
}
ReactDOM.render(
<App tracklist={tracklist} />,
document.getElementById('root')
);
html, body {
margin: 0;
padding: 5px 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 3fr;
gap: 20px;
}
.list-item {
cursor: pointer;
}
.list-item:hover {
background: lightgray;
}
.tracklist {
border: 1px solid gray;
}
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>

How to position a div relatively to an image?

I have a page for a portfolio that does contain a grid that contain images with an info overlay.
Here's the link: cyrilmoisson-dev.netlify.app
Is there a solution to make the overlay div exactly the same size (height and width) as the image without using something like background: url(...);
The problem is that images are random sized...
This question is not a duplicate of this one because it hasn't been resolved for me.
Here is the component code for every image:
src/component/ImageWithInfos/ImageWithInfos.jsx:
// Lazyload
import LazyLoad from 'react-lazyload';
// Style
import { ImageContainer, ImageSrc, ImageInfoContainer, ImageInfo } from './styles';
// Utils
import PropTypes from 'prop-types';
import { v4 as uuid } from 'uuid';
const ImageWithInfos = ({ height, width, src, title, infos }) => (
<LazyLoad height={height} offset={height + 100}>
<ImageContainer height={height} width={width}>
<ImageSrc src={src} alt={title} />
<ImageInfoContainer>
<ImageInfo main>{title}</ImageInfo>
{infos.map((info) => <ImageInfo key={uuid()}>{info}</ImageInfo>)}
</ImageInfoContainer>
</ImageContainer>
</LazyLoad>
);
ImageWithInfos.propTypes = {
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
src: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
infos: PropTypes.array,
};
export default ImageWithInfos;
src/component/ImageWithInfos/styles.js
// Style
import styled, { css } from 'styled-components';
export const ImageContainer = styled.div`
height: ${({ height }) => `${height}px`};
width: ${({ width }) => `${width}px`};
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
`;
export const ImageSrc = styled.img`
display: block;
object-fit: contain;
width: 100%;
height: 100%;
`;
export const ImageInfoContainer = styled.div`
z-index: 5;
position: absolute;
bottom: 0;
height: 100%;
width: 100%;
opacity: 0;
transition: 1s ease;
background-color: black;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
&:hover {
opacity: 1;
background-color: rgba(0, 0, 0, .7);
scale: 1.1;
}
`;
export const ImageInfo = styled.span`
padding: .2rem 1rem;
color: whitesmoke;
text-align: center;
text-transform: capitalize;
${({ main }) => main && css`
font-weight: 800;
`}
`;
src/component/ImageWithInfos/index.js
export { default } from './ImageWithInfos';
Thanks for your help.
BTW: I'm using react and styled-components, if it changes anything...
I believe you could place both the image and the overlay in the same div and have the overlay element cover the whole parent div:
<div className="parent">
<img />
<div className="overlay"></div>
</div>
.parent {
position: relative;
}
.overlay {
position: absolute;
width: 100%;
height: 100%;
}

Styled Component only pass some value to custom component

Hi I'm trying to configure the colour of a button base on it's button type which will be put in a modal. In this case, either "Success" or "Danger":
import React from "react";
import styled from "styled-components";
const ButtonStyled = styled.button`
background-color: ${(props) =>
props.btnType === "Success" ? "green" : "red"};
border: none;
color: white;
outline: none;
cursor: pointer;
font: Regular 400;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
font-weight: bold;
margin-left: 0;
padding-left: 0;
`;
const Button = (props) => {
console.log(props.btnType);
return (
<ButtonStyled btnType={props.btnType} onClick={props.clicked}>
{props.children}
</ButtonStyled>
);
};
export default Button;
Here's where I called the button:
import React from "react";
import Button from "../../UI/Button/Button";
const OrderSummary = (props) => {
return (
<div>
<Button btnType="Danger" clicked={props.cancelPurchase}>
Cancel
</Button>
<Button btnType="Success" clicked={props.continuePurchase}>
Continue
</Button>
</div>
);
};
export default OrderSummary;
Here is where the Order Summary being called:
import React, { useState } from "react";
import Modal from "../../components/UI/Modal/Modal";
import OrderSummary from "../../components/Burger/OrderSummary/OrderSummary";
const INGREDIENT_PRICES = {
salad: 0.5,
cheese: 0.4,
meat: 1.3,
bacon: 0.7,
};
const BurgerBuilder = () => {
const [ingredients, setIngredients] = useState({
salad: 0,
bacon: 0,
cheese: 0,
meat: 0,
});
const [totalPrice, setTotalPrice] = useState(4);
const [purchasing, setPurchasing] = useState(false);
const purchaseCancelHandler = () => {
setPurchasing(false);
};
const purchaseContinueHandler = () => {
alert("continue");
};
return (
<div>
<Modal show={purchasing} modalClosed={purchaseCancelHandler}>
<OrderSummary
continuePurchase={purchaseContinueHandler}
cancelPurchase={purchaseCancelHandler}
ingredients={ingredients}
price={totalPrice.toFixed(2)}
/>
</Modal>
</div>
);
};
export default BurgerBuilder;
Down here is the modal where the Button being applied
import React from "react";
import styled from "styled-components";
import Backdrop from "../Backdrop/Backdrop";
const ModalWrapper = styled.div`
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.2);
background: #fff;
justify-content: center;
align-items: center;
color: #000;
display: grid;
grid-template-columns: 1fr 1fr;
position: fixed;
z-index: 500;
border-radius: 10px;
transition: all 0.3s ease-out;
padding: 16px;
left: 15%;
top: 30%;
#media (min-width: 600px) {
width: 500px;
left: calc(50% - 250px);
}
`;
const ModalContent = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
line-height: 1.8;
color: #141414;
p {
margin-bottom: 1rem;
}
button {
padding: 10px 24px;
background: #141414;
// color: #fff;
border: none;
}
`;
const Modal = (props) => {
return (
<div>
<Backdrop show={props.show} clicked={props.modalClosed} />
<ModalWrapper
style={{
transform: props.show ? "translateY(0)" : "translateY(-100vh)",
opacity: props.show ? "1" : "0",
}}
>
<ModalContent>{props.children}</ModalContent>
</ModalWrapper>
</div>
// <ModalStyled>{props.children}</ModalStyled>
);
};
export default Modal;
As I did some testing, the values in the styled component Modal Content which state the button in that content will be that. Hence it affected the value of the styled component Button Styled. However, if I removed the value of button in Modal Content, the value of the color in ButtonStyled won't be accepted. Does anyone happen to know why?
The button style defined in the ModalContent is overriding the button style you've defined in your Button component.
button {
padding: 10px 24px;
background: #141414; // <-- overrides button background color
border: none;
}
With CSS the most specific selector will be the one that sets the style and I suspect the ModalContent styled component's computed style results with a more specific selector for button elements than the one from ButtonStyled.
What you can do is bump the selector specificity of the ButtonStyled background color.
Pseudoelements, pseudoselectors, and nesting, the last section.
Finally, the ampersand can be used to increase the specificity of
rules on the component; this can be useful if you are dealing with a
mixed styled-components and vanilla CSS environment where there might
be conflicting styles
const ButtonStyled = styled.button`
&& {
background-color: ${(props) =>
props.btnType === "Success" ? "green" : "red"};
}
border: none;
color: white;
outline: none;
cursor: pointer;
font: Regular 400;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
font-weight: bold;
margin-left: 0;
padding-left: 0;
`;

Dynamically setting height of React rendered table in Firefox differs from Chrome

working on a React project currently, using webpack, webpack-dev-server, hot module reloading, React Router, styled-components etc
I have created a Table component where I try to dynamically determine the number of rows to render within the Table based on the height of the parent. Within Chrome, this works as expected, but in Firefox I am finding that all my rows are being rendered and therefore are not bound within the parent component.
Is this a known issue or are there any suggested workarounds or is there something horrendously wrong with my code?
Parent component (App):
const MainContainer = styled.div`
background-color: ${colours.white};
border-radius: 4px;
box-shadow: 0 0 9px 0 #dedede;
display: flex;
flex-wrap: wrap;
height: 85%;
width: 92.5%;
`;
const InnerContainer = styled.div`
display: flex;
flex-direction: column;
width: calc(100% - 9.375em);
`;
const SearchAndCountPlaceholder = styled.div`
height: 12.5%;
`;
const SidebarPlaceholder = styled.div`
background-color: ${colours.blue.light};
height: 100%;
opacity: 0.12;
width: 9.375em;
`;
const LoadMoreButtonContainerPlaceholder = styled.div`
height: 15%;
`;
const App = () => (
<MainContainer className="app-component">
<SidebarPlaceholder />
<InnerContainer>
<SearchAndCountPlaceholder />
<Table tableHeadings={tableHeadings} masterData={mockData} />
<LoadMoreButtonContainerPlaceholder />
</InnerContainer>
</MainContainer>
);
Table component:
const Container = styled.div`
background-color: ${colours.white};
height: 100%;
overflow-x: scroll;
padding-bottom: 1em;
width: 100%;
`;
const StyledTable = styled.table`
border-bottom: 2px solid ${colours.grey.lighter};
border-collapse: collapse;
margin: 1.25em;
table-layout: fixed;
width: 100%;
& thead {
border-bottom: inherit;
color: ${colours.grey.lighter};
font-size: 0.75em;
font-weight: 700;
line-height: 1em;
text-align: left;
text-transform: uppercase;
& th {
padding: 0.75em 1em 0.75em 1em;
width: 7.25em;
}
& th:not(.Source) {
cursor: pointer;
}
& span {
color: ${colours.blue.dark};
font-size: 1em;
font-weight: 300;
margin-left: 0.313em;
}
}
& tbody {
color: ${colours.grey.dark};
font-size: 0.813em;
line-height: 1.125em;
& tr {
border-bottom: 1px solid ${colours.grey.lightest};
& td {
padding: 1em;
}
}
& .masterData {
font-weight: 700;
}
}
`;
let numberOfRowsToDisplay;
const calculateNumberOfRowsToDisplay = () => {
const tableHeight = document.querySelector('.table-component').offsetHeight;
const rowHeight = 40; // height of row in pixels
const numberOfRowsNotToIncludeInCalculation = 2; // header & scrollbar
numberOfRowsToDisplay = Math.floor(
tableHeight / rowHeight - numberOfRowsNotToIncludeInCalculation
);
};
class Table extends Component {
constructor(props) {
super(props);
this.state = { columnToSort: '' };
this.onColumnSortClick = this.onColumnSortClick.bind(this);
}
componentDidMount() {
calculateNumberOfRowsToDisplay();
}
onColumnSortClick(event) {
event.preventDefault();
const columnToSort = event.target.className;
this.setState(prevState => {
if (prevState.columnToSort === columnToSort) {
return { columnToSort: '' };
}
return { columnToSort };
});
}
render() {
const { tableHeadings, masterData } = this.props;
const { columnToSort } = this.state;
const upArrow = '⬆';
const downArrow = '⬇';
return (
<Container className="table-component">
<StyledTable>
<thead>
<tr>
{tableHeadings.map(heading => (
<th
className={heading}
key={heading}
onClick={this.onColumnSortClick}
>
{heading}{' '}
{heading !== 'Source' ? (
<span>
{heading === columnToSort ? upArrow : downArrow}
</span>
) : null}
</th>
))}
</tr>
</thead>
<tbody>
{masterData &&
masterData.slice(0, numberOfRowsToDisplay).map(data => {
const dataKey = uuidv4();
return (
<tr className="masterData" key={dataKey}>
<td>Master</td>
{Object.values(data).map(datum => {
const datumKey = uuidv4();
return <td key={datumKey}>{datum}</td>;
})}
</tr>
);
})}
</tbody>
</StyledTable>
</Container>
);
}
}
Thanks in advance!
Maybe not the answer I was quite looking for, but in the end I have set the dynamic numberOfRowsToDisplay as part of the component's state; which has given me the UI result I was looking for

Categories

Resources