I have a multi-tier, multi-column menu that has been built using https://github.com/kontentino/react-multilevel-dropdown. The problem I am having is that the submenu items keep appearing underneath the main items. I have tried multiple attempts using z-index but nothing seems to be helping.
I am including my menu component, my CSS for this page as well as an image of the problem.
If possible I want to avoid trying to roll a custom menu using lists, but am willing to entertain another multi-level menu package.
MenuBarComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Col from 'react-bootstrap/Col';
import Row from 'react-bootstrap/Row';
import Container from 'react-bootstrap/Container';
import { NavLink } from 'react-router-dom';
// import { CurrentPage_Update } from '../../redux/ActionCreators';
import Dropdown from 'react-multilevel-dropdown';
import { Loading } from './LoadingComponent';
import '../../shared/styles/menu.css'
const mapStateToProps = (state) => {
return {
siteMap: state.siteMap
}
}
const mapDispatchToProps = (dispatch) => {
return {
// CurrentPage_Update: (page) => { dispatch(CurrentPage_Update(page)) }
}
}
class MenuBar extends Component {
constructor(props) {
super(props);
this.state = {
openCount: 1,
isOpen: false
}
}
drawLink = (item) => {
return (
<NavLink
to={'/page_' + item.pageId}
className={`menu menu-link`}
key={"DDNavLink_" + item.pageId}
onClick={() => this.setState({ openCount: -1 })}
>{item.title}</NavLink>
);
}
NavListItem = (item, level, roles) => {
}
dropdownItem = (item, level, roles) => {
let brk = true;
//TODO: remove next line for production
item.roles = "*," + item.roles; //Make sure page is shown in development
if ((item.roles.indexOf("*") < 0) && (item.roles.indexOf("*") < 0)) {
var itemRoles = item.roles.toLowerCase().split(',');
for (var i = 0; i < itemRoles.length; i++) {
if (roles.indexOf(itemRoles[i]) > -1) {
//if found
brk = false;
break;
}
}
if (brk) return;
}
if (item.children.length > 0) {
//if any visible children show dropdown
//only endpoints are clickable
if (item.children.find((child) => { return child.visible !== "false" })) {
return (
<Dropdown.Item key={'Dropdown_' + item.pageId} className={`menu menu-item-container`}>
AA{item.title}
<Dropdown.Submenu position='right' className={`menu-UncontrolledDropdown sub${level + 1}`} >
{item.children.map((listItem) => {
return (this.dropdownItem(listItem, level + 1, roles))
})
}
</Dropdown.Submenu>
</Dropdown.Item>
)
}
else { //otherwise if no visible children show only the item
if (item.visible !== "false") {
return (
<Dropdown className={`sub${level + 1} menu menu-item-container`
} key={"DDItem_" + item.pageId} >
BB{level} { this.drawLink(item)}
</Dropdown >
)
}
}
}
else { //if no children show only item
return (
<Dropdown.Item className={`sub${level} menu menu-item-container`} key={"DDItem" + item.pageId}>
CC{level}
{this.drawLink(item, level)}
</Dropdown.Item>
)
}
}
render() {
// const setIsOpen = (value) => { this.setState({ isOpen: value }) }
// const toggle = () => { setIsOpen(!this.state.isOpen) };
if (this.props.siteMap.errMess) {
return (
<div>
{this.props.siteMap.errMess}
</div>
)
}
else if (this.props.siteMap.isLoading) {
return <Loading title="Site Menu" />
}
else {
var roles = this.props.siteMap.siteMapData.userRoles.toLowerCase();
var siteMap = this.props.siteMap.siteMapData.siteMap;
return (
<Container>
<Row>
<Col className="col-2 p-3 menu">
<NavLink to='/'>
<div className="align-top met-logo"></div>
</NavLink>
</Col>
<Col md={{ span: 9, offset: 1 }} className=" menu ">
{
siteMap.map((link, index) => {
return <div
key={index}
className='p-1 column menu menu-item-container menu-head'>
{this.dropdownItem(link, 0, roles)}
</div>
})
}
{/* https://github.com/kontentino/react-multilevel-dropdown#readme */}
</Col>
</Row>
</Container>
);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MenuBar);
menu.css
/*Necessary to force dropdown items into a straight line */
nav>ul>li>a+div>ul>li>a {
text-indent: 15px;
/* Can use padding-left as well */
}
.met-logo {
background-image: url('../images/METLogo.jpg');
background-size: 100%;
background-repeat: no-repeat;
/* width: 128px;
height: 91px; */
width: 100px;
height: 65px;
/* border: 1px solid black; */
top: -5px;
padding: 3px;
}
.menu {
color: #000;
position: sticky;
top: 5px;
/* z-index: 1000000; */
/* border:solid 1px black; */
}
.menu-collapse {
position: absolute;
top: 10px;
}
.menu-dropdown-toggle {
color: #000;
}
.menu-item-container {
color: #000;
background-color: #ccc;
border: solid 1px #555;
white-space: pre-wrap;
}
.menu-item-container :hover {
background-color: #aaa;
}
.menu-link {
color: #000;
cursor: pointer;
}
.menu-bar {
color: #000;
}
/* .menu-toggler {
float: right;
margin-right: 10px;
} */
.menu-dropdown-container {
color: #000;
padding-top: 0px;
padding-bottom: 0px;
margin-top: 15px;
overflow: wrap;
}
.menu-dropdown-container ul {
box-sizing: content-box;
background-color: #fff !important;
border: 0px;
overflow: wrap;
}
.menu-link {
color: #000;
}
.menu-dropdown-title {
color: #000;
}
.menu-dropdown-body {
color: #000;
}
.menu-head {
z-index: 9000!important;
}
.menu-UncontrolledDropdown {
color: #000;
background-color: #fff!important;
border: solid 1px #555;
transform: translate(-10%, 15px);
/* z-index: 99999!important; */
/* position: absolute; */
}
.sub0 {
z-index: 12000;
}
.sub1 {
z-index: 13000;
}
.sub2 {
z-index: 14000;
}
.sub3 {
z-index: 15000;
}
.sub4 {
z-index: 16000;
}
.menu-UncontrolledDropdown :hover {
background-color: #bbf;
}
.menu-UncontrolledDropdown>div {
background-color: #fff;
}
/* .fullWidth {
width: 100vw!important;
margin: 2px 2px 2px 2px;
}*/
.column {
display: inline-block;
padding: 8px;
font: 20px;
font-weight: 500;
}
----------
And here is the example of what the problem is.
I am not using JQuery in this project but React-Bootstrap is used.
After going through the code I finally removed all positioning and z-index from my CSS sheet, leaving in the translates for correct positioning. Leaving the implementation to the component code rather than my own has solved the problems including overlay on the menu column next to the dropdown.
Related
In this case, I used React + TypeScript and ant-design. The following code works perfectly but I want the codes to be summarized as much as possible. This is about starting a site that has 3 pages. For example, how can I write this part (const { id, title, description, background } = splash;) so that I don't need to define (splashs[index].background , splashs[index].title, splashs[index].description) all the time.
Thank you in advance for your cooperation.
.splash {
height: 100vh;
position: relative;
overflow: hidden;
}
.bg {
background-color: var(--cjp);
}
.BgGradiant {
background: linear-gradient(107.78deg, rgba(80, 21, 100, 0) 1.87%, rgba(80, 21, 100, 0.05) 18.6%, rgba(80, 21, 100, 0.51) 25.79%, #1C3396 99.02%, #1C3396 51.08%);
}
.context{
width: 80%;
}
.content {
text-align: center;
}
.content h1,
.content p {
color: var(--cwh);
}
.backgroundImage>img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
-o-object-fit: cover;
object-fit: cover;
}
.logo {
width: 100%;
text-align: center;
}
.btns {
display: flex !important;
align-items: center;
justify-content: space-between;
-webkit-margin-start: auto;
margin-inline-start: auto;
-webkit-margin-end: auto;
margin-inline-end: auto;
-webkit-margin-before: 2rem;
margin-block-start: 7rem;
}
.btns :global(.ant-btn){
background-color: var(--cwh);
border-radius: var(--borderRadius12);
position: relative;
padding: 4px 10px !important;
}
.btns :global(.ant-btn)::after{
content: "";
position: absolute;
width: 125%;
height: 125%;
top: 50%;
left: 50%;
border: 1px solid var(--chb);
border-radius: var(--borderRadius14);
transform: translate(-50%, -50%);
}
.btns :global(.ant-btn > span){
margin-left: 0 !important;
}
.btns :global(.ant-btn > span > svg){
fill: var(--cal);
}
.btnSkip {
background-color: unset;
outline: none;
border: none;
color: var(--cca);
}
.btnLogin{
-webkit-margin-before: 2rem;
margin-block-start: 7rem;
}
.btnLogin :global(.ant-btn){
border-radius: var(--borderRadius10);
background-color: var(--cwh);
color: var(--cjp);
}
.btnLogin :global(.ant-btn > span){
font-family: "Display-Bold";
}
.dots {
position: absolute;
bottom: 17%;
display: flex !important;
align-items: center;
justify-content: center;
left: 50%;
transform: translateX(-50%);
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dotActive {
background-color: var(--cwh);
}
.dotDeActive {
background-color: var(--cca);
}
.dot:not(:last-child) {
-webkit-margin-end: 0.5rem;
margin-inline-end: 0.5rem;
}
.contentInner{
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
-webkit-padding-before: 2rem;
padding-block-start: 2rem;
-webkit-padding-after: 3rem;
padding-block-end: 3rem;
}
.contentInner1{
justify-content: space-between;
}
.contentInner2{
justify-content: flex-end;
}
import React, { useState } from 'react';
import { useNavigate } from "react-router-dom";
import { Row, Col, Button } from 'antd';
import { ArrowRightOutlined } from '#ant-design/icons';
import Container from '../../Components/UI/Container/Container'
import classes from './Splash.module.css';
import { backgroundSplash1, backgroundSplash2, logoImage } from '../../Assets/index';
const Splash = () => {
let navigate = useNavigate();
const [index, setIndex] = useState<number>(0);
const {
splash,
bg,
BgGradiant,
context,
content,
backgroundImage,
logo,
btns,
btnLogin,
btnSkip,
dots,
dot,
dotActive,
dotDeActive,
contentInner,
contentInner1,
contentInner2,
} = classes
const splashs = [
{
id: 0,
title: 'Page 1 : title 1',
desctiption: '1- Lorem ipsum 1 ',
background: logoImage,
},
{
id: 1,
title: 'Page 2 : title 2',
desctiption: '2- Lorem ipsum 2 ',
background: backgroundSplash1,
},
{
id: 2,
title: 'Page 3 : title3',
desctiption: '3- Lorem ipsum 3',
background: backgroundSplash2,
}
];
const nextBnt = () => {
setIndex(index + 1);
if (index === splashs.length - 1) {
return navigate("/login");
}
}
const skipBtn = () => {
console.log('skip ');
return navigate("/login");
}
const loginBtn = () => {
return navigate("/login");
}
return (
<>
<Row>
<Col xs={24}>
<section
className={`${index === 0 ? bg : BgGradiant} ${splash}`}>
{
splashs.map((splash) => {
const { id, title, desctiption, background } = splash;
console.log(title, "title");
return (
<>
{
index !== 0 && (
<div className={backgroundImage}>
<img src={splashs[index].background} />
</div>
)
}
<Container key={id} className={backgroundImage}>
<div className={`${index === 0 ? contentInner1 : contentInner2} ${contentInner}`}>
{
index === 0 && (
<div className={logo}>
<img src={logoImage} alt="logoImage" />
</div>
)
}
<div className={context}>
<div className={content}>
<h1>{splashs[index].title}</h1>
<p>{splashs[index].desctiption}</p>
</div>
{/* BTNS */}
{
index === splashs.length - 1 ? (
<div className={btnLogin}>
<Button block onClick={loginBtn}>Login</Button>
</div>
) : (
<div className={btns}>
<button className={btnSkip} onClick={skipBtn}>skip</button>
<Button onClick={nextBnt}> <ArrowRightOutlined /></Button>
</div>
)
}
</div>
</div>
</Container>
</>
)
})
}
<div className={dots}>
{
Array.from({ length: 3 }).map((item, idx) => {
return (
<div key={idx} className={`${dot} ${index === idx ? dotActive : dotDeActive}`}></div>
)
})
}
</div>
</section>
</Col>
</Row>
</>
)
}
export default Splash;
Just an advice, your question isn't really well formulated so it's hard to understand what are you trying to accomplish. Try to keep the questions clear and remove any redundant code so the community can better understand it.
If I'm assuming correctly that you don't want to use splash[index] then you should change splash[index].title to title, same for the other props.
Since you already destructured the splash object with const { id, title, description, background } = splash; all these will be available.
Another thing here is, .map method returns the item in the array so I don't see the point in you using the index inside the loop to access the item from the array.
How do I make a child component's border go on top (higher z-index) of its parent component's border? Z-index didn't work for me.
Wanted behavior: clicking on the tab enables green border-bottom to show that the tab is clicked. This green border overlaps on top of the default gray border that is set throughout all the tabs.
https://codesandbox.io/s/navbar-component-d5f1c?file=/Navbar.js
import React, { useState } from 'react';
import styled from "styled-components";
const Navbar = ({ value, children, tabFilter, contentFilter }) => {
const [activeTab, setActiveTab] = useState(value[0].title);
const onClickTabItem = tab => {
setActiveTab(tab);
}
return (
<React.Fragment>
<NavbarOutline id="main">
<ol>
{value.map(child => {
const { title } = child;
return <Tab activeTab={activeTab} key={title} title={title} handleClick={onClickTabItem} />;
})}
</ol>
</NavbarOutline>
<div>
{value.map(child => {
if (child.title !== activeTab) return undefined;
return <StyledTabs className="content">{child.title}</StyledTabs>
})}
</div>
</React.Fragment>
);
}
const Tab = props => {
const { activeTab, title, handleClick } = props;
let className = 'not-active';
const onClick = () => {
handleClick(title);
};
if (activeTab === title) {
className = 'active';
}
return (
<StyledTabs className={className} onClick={onClick}>
{title}
</StyledTabs>
);
};
// eslint-disable-next-line import/prefer-default-export
export const NavbarOutline = styled.div`
margin-left: 35px;
margin-right: 35px;
overflow-x: auto;
white-space: nowrap;
border-bottom: 2px solid #e3e3e3;
top: 0px;
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
&::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
`;
const StyledTabs = styled.button.attrs(props => ({
className: props.className
}))`
&.not-active {
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 20px;
padding: 16px 31px 16px 31px;
background: none;
border: none;
position: relative;
bottom: -29px;
}
&.active {
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 20px;
position: relative;
bottom: -29px;
z-index: 3;
background: none;
color: #2b8000;
border: none;
border-bottom: 3px solid #2b8000;
}
&.content {
background: none;
border: none;
}
`;
The parent div with id="main" has overflow-x: auto, which is causing your child tab element to disappear when it goes outside of it. It's probably what you want, but that's why you can't see the green border.
Open the devtools and untick overflow-x: auto to see for yourself.
I'm quite new to react and a simple slot machine is my first mini-project. Currently I have completed the logic for displaying random emojis after the button is pressed. The next step for me, before styling and adding logic for winning/losing and a counter for coins etc is adding an animation.
https://codepen.io/fmressel/pen/vRLerN
This codepen is exactly the sort of thing I'm after, but as you can see it is structured quite differently to my code (below), and I'm pulling my hair out trying to figure out how I can get something similar to work for my project.
Any help much appreciated!
import React, { Component } from 'react'
import Contents from './Contents'
import './mainslots.css'
class Slots extends Component {
static defaultProps = {
fruits: ["🍒", "🍉", "🍊", "🍓", "🍇", "🥝"]
};
constructor(props) {
super(props);
this.state = {fruit1: '🍒', fruit2: '🍒', fruit3: '🍒', rolling: false};
this.roll = this.roll.bind(this);
};
roll = () => {
const dFruit1 = this.props.fruits[
Math.floor(Math.random() * this.props.fruits.length)
];
const dFruit2 = this.props.fruits[
Math.floor(Math.random() * this.props.fruits.length)
];
const dFruit3 = this.props.fruits[
Math.floor(Math.random() * this.props.fruits.length)
];
this.setState({fruit1: dFruit1, fruit2: dFruit2, fruit3: dFruit3, rolling: true});
setTimeout(() => {
this.setState({ rolling: false });
}, 700)
}
render(){
return(
<div className="SlotMachine">
<div className="SlotsContainer">
{this.state.fruit1}
{this.state.fruit2}
{this.state.fruit3}
</div>
<button className="spinner" onClick={this.roll} disabled={this.state.rolling}>
{this.state.rolling ? "Spinning..." : "Spin"}
</button>
</div>
);
}
}
export default Slots;
import React, { Component } from 'react'
class Contents extends Component {
Fruits = ["🍒", "🍉", "🍊", "🍓", "🍇", "🥝"];
render() {
return(
<div className="emptys">
{this.props.roll}
</div>
)
}
}
export default Contents
Here you go,
It was fun to develop :), you can run the below code snippet to review and I've added the comments in code, that will make things clear, please have a look,
Hope this will help,
const { createRef , Component } = React;
class Slots extends Component {
static defaultProps = {
fruits: ["🍒", "🍉", "🍊", "🍓", "🍇", "🥝"]
};
constructor(props) {
super(props);
this.state = { fruit1: "🍒", fruit2: "🍒", fruit3: "🍒", rolling: false };
// get ref of dic onn which elements will roll
this.slotRef = [createRef(), createRef(), createRef()];
}
// to trigger roolling and maintain state
roll = () => {
this.setState({
rolling: true
});
setTimeout(() => {
this.setState({ rolling: false });
}, 700);
// looping through all 3 slots to start rolling
this.slotRef.forEach((slot, i) => {
// this will trigger rolling effect
const selected = this.triggerSlotRotation(slot.current);
this.setState({ [`fruit${i + 1}`]: selected });
});
};
// this will create a rolling effect and return random selected option
triggerSlotRotation = ref => {
function setTop(top) {
ref.style.top = `${top}px`;
}
let options = ref.children;
let randomOption = Math.floor(
Math.random() * Slots.defaultProps.fruits.length
);
let choosenOption = options[randomOption];
setTop(-choosenOption.offsetTop + 2);
return Slots.defaultProps.fruits[randomOption];
};
render() {
return (
<div className="SlotMachine">
<div className="slot">
<section>
<div className="container" ref={this.slotRef[0]}>
{Slots.defaultProps.fruits.map((fruit, i) => (
<div key={i}>
<span>{fruit}</span>
</div>
))}
</div>
</section>
</div>
<div className="slot">
<section>
<div className="container" ref={this.slotRef[1]}>
{Slots.defaultProps.fruits.map(fruit => (
<div>
<span>{fruit}</span>
</div>
))}
</div>
</section>
</div>
<div className="slot">
<section>
<div className="container" ref={this.slotRef[2]}>
{Slots.defaultProps.fruits.map(fruit => (
<div>
<span>{fruit}</span>
</div>
))}
</div>
</section>
</div>
<div
className={!this.state.rolling ? "roll rolling" : "roll"}
onClick={!this.state.rolling && this.roll}
disabled={this.state.rolling}
>
{this.state.rolling ? "Rolling..." : "ROLL"}
</div>
</div>
);
}
}
ReactDOM.render(<Slots />, document.getElementById('react-root'));
.App {
font-family: sans-serif;
text-align: center;
}
.slot {
position: relative;
display: inline-block;
height: 100px;
width: 80px;
}
section {
position: absolute;
border-radius: 15px !important;
border: 3px solid black !important;
width: 70px;
height: 70px;
overflow: hidden;
background-color: grey;
border-radius: 2px;
border: 1px solid lightgrey;
color: white;
font-family: sans-serif;
text-align: center;
font-size: 25px;
line-height: 60px;
cursor: default;
}
.container {
position: absolute;
top: 2px;
width: 100%;
transition: top ease-in-out 0.5s;
text-align: center;
}
.roll {
width: 215px;
cursor: pointer;
background-color: yellow;
padding: 10px;
text-align: center;
font-size: 20px;
border-radius: 20px;
border: 3px solid black;
}
.rolling {
animation: blinkingText 1.2s infinite;
}
#keyframes blinkingText {
0% {
color: #000;
}
49% {
color: #000;
}
60% {
color: transparent;
}
99% {
color: transparent;
}
100% {
color: #000;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
I recreated the #VivekDoshi 's answer as a react FC.
const { useRef, useState} = React;
function Slots(){
const [fruit1,setFruit1] = useState("🍒");
const [fruit2,setFruit2] = useState("🍒");
const [fruit3,setFruit3] = useState("🍒");
const [rolling,setRolling] = useState(false);
let slotRef = [useRef(null), useRef(null), useRef(null)];
const fruits = ["🍒", "🍉", "🍊", "🍓", "🍇", "🥝"]
// to trigger roolling and maintain state
const roll = () => {
setRolling(true);
setTimeout(() => {
setRolling(false);
}, 700);
// looping through all 3 slots to start rolling
slotRef.forEach((slot, i) => {
// this will trigger rolling effect
const selected = triggerSlotRotation(slot.current);
if(i+1 == 1)
setFruit1(selected);
else if(i+1 == 2)
setFruit2(selected);
else
setFruit3(selected);
});
};
// this will create a rolling effect and return random selected option
const triggerSlotRotation = ref => {
function setTop(top) {
ref.style.top = `${top}px`;
}
let options = ref.children;
let randomOption = Math.floor(
Math.random() * fruits.length
);
let choosenOption = options[randomOption];
setTop(-choosenOption.offsetTop + 2);
return fruits[randomOption];
};
return (
<div className="SlotMachine">
<div className="slot">
<section>
<div className="container" ref={slotRef[0]}>
{fruits.map((fruit, i) => (
<div key={i}>
<span>{fruit}</span>
</div>
))}
</div>
</section>
</div>
<div className="slot">
<section>
<div className="container" ref={slotRef[1]}>
{fruits.map(fruit => (
<div>
<span>{fruit}</span>
</div>
))}
</div>
</section>
</div>
<div className="slot">
<section>
<div className="container" ref={slotRef[2]}>
{fruits.map(fruit => (
<div>
<span>{fruit}</span>
</div>
))}
</div>
</section>
</div>
<div
className={!rolling ? "roll rolling" : "roll"}
onClick={!rolling && roll}
disabled={rolling}>
{rolling ? "Rolling..." : "ROLL"}
</div>
</div>
);
};
ReactDOM.render(<Slots />, document.getElementById('react-root'));
.App {
font-family: sans-serif;
text-align: center;
}
.slot {
position: relative;
display: inline-block;
height: 100px;
width: 80px;
}
section {
position: absolute;
border-radius: 15px !important;
border: 3px solid black !important;
width: 70px;
height: 70px;
overflow: hidden;
background-color: grey;
border-radius: 2px;
border: 1px solid lightgrey;
color: white;
font-family: sans-serif;
text-align: center;
font-size: 25px;
line-height: 60px;
cursor: default;
}
.container {
position: absolute;
top: 2px;
width: 100%;
transition: top ease-in-out 0.5s;
text-align: center;
}
.roll {
width: 215px;
cursor: pointer;
background-color: yellow;
padding: 10px;
text-align: center;
font-size: 20px;
border-radius: 20px;
border: 3px solid black;
}
.rolling {
animation: blinkingText 1.2s infinite;
}
#keyframes blinkingText {
0% {
color: #000;
}
49% {
color: #000;
}
60% {
color: transparent;
}
99% {
color: transparent;
}
100% {
color: #000;
}
}
<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-root"></div>
I have a simple React App, where I fetch the Flickr Public Feed API and display it. Unfortunately it is mapping the array several times, where I can see repeated photos. The request always returns an array with 20 items with the same pictures, explaining the repetition.
Check the code below:
import React, { Component } from 'react';
import $ from 'jquery';
import PhotoListItem from '../../components/photoListItem';
import Searchbar from '../../components/searchBar';
import ScrollButton from '../../components/scrollButton';
import '../app/index.css';
export default class PhotoApp extends Component {
constructor(props) {
super(props);
this.state = {
photoList: [],
searchTerm: 'cyanotype',
items: 10,
loadingState: false,
}
}
componentDidMount() {
this.getPhotoList();
this.onInfiniteScroll();
}
/* get data from Flickr public feed */
getPhotoList = () => {
const flickrApiPoint = "https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?&tags=" + this.state.searchTerm;
try {
$.ajax({
url: flickrApiPoint,
dataType: 'jsonp',
data: { format: "json" },
success: function (data) {
this.setState({ photoList: data.items });
}.bind(this)
});
}
catch (err) {
console.log(err);
}
}
/* code for infinite scroll */
onInfiniteScroll = () => {
this.refs.iScroll.addEventListener("scroll", () => {
if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >= this.refs.iScroll.scrollHeight - 20) {
this.loadMoreItems();
}
});
}
/* */
displayItems = () => {
var items = [];
for (var i = 0; i < this.state.items; i++) {
items.push(
this.state.photoList.map((photo, index) => {
const author = photo.author.split(/"/)[1];
const authorLink = photo.description.split(/"/)[1]
const description = photo.description.split(/"/)[13]
return (
<PhotoListItem
key={index}
url={photo.media.m}
photoLink={photo.link}
title={photo.title}
author={author}
authorLink={authorLink}
description={description}
tags={photo.tags} />
)
})
);
}
return items;
}
/* */
loadMoreItems = () => {
if (this.state.loadingState) {
return;
}
this.setState({ loadingState: true });
setTimeout(() => {
this.setState({ items: this.state.items + 10, loadingState: false });
}, 1000);
}
render() {
return (
<div className='appContainer' ref="iScroll">
<div className='appHeader'>
<h1 className='headerTitle'>Welcome to Flickr Alternative Photography Feed!</h1>
</div>
<div className='gridContainer'>
{this.displayItems()}
</div>
{this.state.loadingState ? <p className='loading'>Loading items...</p> : ""}
</div>
);
}
}
HERE IS THE LIVE EXAMPLE
The problem is around this.displayItems(), but how can I fix this?
Any help is appreciated. Thank you!
You can achieve this by slicing your array by the amount of items you want to show within your JSX :
this.state.photoList.slice(0, this.state.items).map(
You will then have to use the callback version of setState to use the old values of your state and increment what you want to show :
this.setState(old => ({ items: old.items + 2, loadingState: false }));
Fully functional example (using the "full page" option is recommended) :
class PhotoListItem extends React.Component {
render() {
return (
<div className="image-card">
<img className="image-card__image" alt="" src={this.props.url} />
<div className="image-card__body">
<div className="image-title">
<a href={this.props.photoLink}>{this.props.title}</a>
<span className="image-author">
{" "}
by <a href={this.props.authorLink}>{this.props.author}</a>
</span>
</div>
<div className="image-description">
<span className="description">Description:</span>{" "}
{this.props.description}
</div>
<div className="image-tags">
<span className="tags">Tags:</span> {this.props.tags}
</div>
</div>
</div>
);
}
}
class PhotoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
photoList: [],
items: 2,
searchTerm: "cyanotype",
loadingState: false
};
}
componentDidMount() {
this.getPhotoList();
this.onInfiniteScroll();
}
/* get data from Flickr public feed */
getPhotoList = () => {
const flickrApiPoint =
"https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?&tags=" +
this.state.searchTerm;
try {
$.ajax({
url: flickrApiPoint,
dataType: "jsonp",
data: { format: "json" },
success: function(data) {
this.setState({ photoList: data.items });
}.bind(this)
});
} catch (err) {
console.log(err);
}
};
/* code for infinite scroll */
onInfiniteScroll = () => {
this.refs.iScroll.addEventListener("scroll", () => {
if (
this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=
this.refs.iScroll.scrollHeight - 20
) {
this.loadMoreItems();
}
});
};
/* */
loadMoreItems = () => {
if (this.state.loadingState) {
return;
}
this.setState({ loadingState: true });
setTimeout(() => {
this.setState(old => ({ items: old.items + 2, loadingState: false }));
}, 1000);
this.getPhotoList();
};
render() {
return (
<div className="appContainer" ref="iScroll">
<div className="appHeader">
<h1 className="headerTitle">
Welcome to Flickr Alternative Photography Feed!
</h1>
</div>
<div className="gridContainer">
{this.state.photoList.slice(0, this.state.items).map((photo, index) => {
const author = photo.author.split(/"/)[1];
const authorLink = photo.description.split(/"/)[1];
const description = photo.description.split(/"/)[13];
return (
<PhotoListItem
key={index}
url={photo.media.m}
photoLink={photo.link}
title={photo.title}
author={author}
authorLink={authorLink}
description={description}
tags={photo.tags}
/>
);
})}
</div>
{this.state.loadingState ? (
<p className="loading">Loading items...</p>
) : (
""
)}
</div>
);
}
}
ReactDOM.render(<PhotoApp />, document.getElementById("root"));
body,
html {
margin: 0;
min-height: 100%;
}
.appContainer {
font-family: "Helvetica", sans-serif;
width: 100%;
height: 100vh;
overflow: auto;
}
.appHeader {
text-align: center;
background-color: #033666;
padding: 1rem;
}
.headerTitle {
color: #fff;
}
.gridContainer {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
padding: 1rem;
grid-gap: 1rem 1rem;
}
.loading {
text-align: center;
color: #033666;
}
#media only screen and (max-width: 320px) {
.appHeader>h1 {
font-size: 1.2rem;
}
}
a,
a:visited {
color: #000;
text-decoration: none;
}
a:hover {
color: #033666;
text-decoration: underline;
}
.image-card {
display: flex;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
flex-direction: column;
width: auto;
height: auto;
margin: .5rem;
border-radius: 5px;
box-shadow: 0 5px 15px rgba(0, 0, 0, .15);
background: #fff;
}
.image-card__image {
border-radius: 5px 5px 0 0;
width: 100%;
height: 200px;
object-fit: cover;
}
.image-card__body {
padding: .5rem 1rem 1rem;
}
.image-title {
font-weight: 600;
margin: 0;
word-wrap: break-word;
padding-bottom: .7rem;
cursor: pointer;
}
.image-author {
font-weight: 100;
font-size: .8rem;
cursor: pointer;
}
.image-owner {
margin-top: 0;
font-size: .8rem;
}
.image-date-view-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
}
.image-description {
padding-bottom: .7rem;
font-size: .9rem;
word-wrap: break-word;
}
.tags,
.description {
font-weight: 600;
}
.image-tags {
font-size: .8rem;
word-wrap: break-word;
}
.App {
font-family: sans-serif;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id="root" />
I have one react component. I am able to adjust the height by adding resize: vertical;
overflow: auto; in my css file. However, I only can adjust by dragging bottom right corner of the border. Is it possible to change it to whole bottom line? Or any other react api can achieve this?
This is an example of how I adjust the height by dragging bottom right corner.
This is my component.
<div class='map'>
<MyMapComponent
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
date ={this.state.date}
getMap={this.getMap}
updateStatus = {this.state.updateStatus}
filter={this.state.filter}
filterList={this.state.filterList}
inputProps={{
classes: {
input: classes.multilineColor
}
}}
>
{/*this.getMap()*/}
</MyMapComponent>
</div>
This is my css file.
.map{
border: 2px solid;
padding: 20px;
width: 300px;
resize: vertical;
overflow: auto;
}
I follow this horizontal resize panel example to create a vertical one.
This is my edited resize panel
import React from "react"
import ReactDOM from "react-dom";
import './ResizablePanels.css';
class ResizablePanels extends React.Component {
eventHandler = null
constructor () {
super()
this.state = {
isDragging: false,
panels: [800, 300, 0]
}
}
componentDidMount () {
ReactDOM.findDOMNode(this).addEventListener('mousemove', this.resizePanel)
ReactDOM.findDOMNode(this).addEventListener('mouseup', this.stopResize)
ReactDOM.findDOMNode(this).addEventListener('mouseleave', this.stopResize)
}
startResize = (event, index) => {
this.setState({
isDragging: true,
currentPanel: index,
initialPos: event.clientY
})
}
stopResize = () => {
if (this.state.isDragging) {
console.log(this.state)
this.setState(({panels, currentPanel, delta}) => ({
isDragging: false,
panels: {
...panels,
[currentPanel]: (panels[currentPanel] || 0) - delta,
[currentPanel - 1]: (panels[currentPanel - 1] || 0) + delta
},
delta: 0,
currentPanel: null
}))
console.log(this.state)
}
}
resizePanel = (event) => {
if (this.state.isDragging) {
//console.log(event.clientY +" "+this.state.initialPos);
const delta = event.clientY - this.state.initialPos
this.setState({
delta: delta
})
}
}
render() {
const rest = this.props.children.slice(1);
// console.log(this.props);
return (
<div className="panel-container" onMouseUp={() => this.stopResize()}>
<div className="panel" style={{height: this.state.panels[0]}}>
{this.props.children[0]}
</div>
{[].concat(...rest.map((child, i) => {
return [
<div onMouseDown={(e) => this.startResize(e, i + 1)}
key={"resizer_" + i}
style={this.state.currentPanel === i+1 ? {top: this.state.delta} : {}}
className="resizer"></div>,
<div key={"panel_" + i} className="panel" style={{height: this.state.panels[i + 1]}}>
{child}
</div>
]
}))}
</div>
)
}
}
export default ResizablePanels;
and this is my css file for this panel
#import url('https://fonts.googleapis.com/css?family=Ubuntu');
html {
background: #333;
font-family: sans-serif;
}
h1 {
color: white;
}
.panel-container {
display: flex;
min-height: 100%;
justify-content: center;
flex-direction: column;
text-align: center;
}
.panel {
background: #EEE;
border: 0px solid gray;
padding: 1px;
}
.panel:first-child {
}
.resizer {
height: 8px;
background: darkGray;
position: relative;
cursor: row-resize;
flex-shrink: 0;
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
user-select: none; /* Likely future */
}
.resizer::after,
.resizer::before {
content: "";
border-left: 1px solid #333;
position: absolute;
top: 50%;
transform: translateX(-100%);
right: 0;
display: inline-block;
height: 20px;
margin: 0 2px;
}
.resizer::before {
top: 0;
}