I'm currently working my way through React, and I'm building a portfolio site. On the work page, I have 3 tiles, which will eventually be overlapping. When a card is clicked, it opens a side panel, and (hopefully) the active card goes to the front, pushing the others behind it to fill in the space.
I currently have the cards, or tabs working, I just need help with how to rearrange them on click.
This is the Tab.js component
import PropTypes from 'prop-types';
class Tab extends Component {
onClick = () => {
const { label, onClick } = this.props;
console.log('that tickles', {onClick})
onClick(label);
}
render() {
const {
onClick,
props: {
activeTab,
label,
position,
inactivePosition,
style
},
} = this;
console.log(this)
let className = 'tab-list-item';
if (activeTab === label) {
className += ' tab-list-active';
}
return (
<div
style={style}
className={className}
onClick={onClick}
>
{label}
</div>
);
}
}
export default Tab;
The tabs.js component that brings things together
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './tabstyle.css'
import Tab from './tab.js';
class Tabs extends Component {
static propTypes = {
children: PropTypes.instanceOf(Array).isRequired,
}
constructor(props) {
super(props);
this.state = {
activeTab: this.props.children[0].props.label,
};
}
onClickTabItem = (tab) => {
this.setState({
activeTab: tab
});
}
render() {
const {
onClickTabItem,
props: {
children,
position,
},
state: {
activeTab,
inactiveTab1,
inactiveTab2,
}
} = this;
return (
<React.Fragment>
{children.map((child) => {
const { label } = child.props;
return (
<Tab
activeTab={activeTab}
key={label}
label={label}
onClick={onClickTabItem}
/>
);
})}
<div className="tab-content">
{children.map((child) => {
if (child.props.label !== activeTab) return undefined;
return child.props.children;
})}
</div>
</React.Fragment>
);
}
}
export default Tabs;
the actual work page
export default function Work() {
return (
<React.Fragment >
<Tabs>
<div label="Example1" position='1'>
what in the world
</div>
<div label="Example2" position='2'>
lorem ipsum
</div>
<div label="Example3" position='3'>
Nothing to see here, this tab is!
</div>
</Tabs>
</React.Fragment>
);
}
}
.tab-list-item {
background-color:#fff;
position: relative;
transition: all .5s;
}
.tab-content {
color: #fff;
background-color: rgb(19, 47, 74);
grid-area:1/13/span 9/ span 8;
z-index: 5;
padding:3em;
}
.tab-list-active {
background-color: #f0f;
grid-area: 2/3/span 4/ span 4;
}
.position2{
grid-area:3/4/span 4/ span 4;
}
.position3{
grid-area:4/5/span 4/ span 4;
}
In my mind, I pictured building an array, and simply having the first card be active, then when another card is clicked, that card is sent to position 0, and so on.
I hope that's everything. As I said, I'm pretty new to React, bit less new to vanilla JavaScript, but still pretty green. Any guidance would be massively appreciated, as I'm at a bit of an impasse at the moment.
For a working demo, I'm using AWS Amplify to host it temporarily https://master.d2wqg4b36m462q.amplifyapp.com/work
Related
Sorry if the phrasing of my question is unclear. I'm new to React and making an app with three different screens/pages. There should be a footer that follows the state of the payment process thanks to 3 boxes. The box that is being selected should show up in another color (here I've used a solid blue border).
When clicking on another box (box 3 for example), I would like the third box to be selected and the other two boxes to be unselected. In order to achieve this, I've set up a state.active in the child element. If the state is active, then a different style is applied to the element (the blue border).
I have two components, "ProgressBar" being the parent, and "ProgressElement" being the child. Here is what the code looks like :
class ProgressElement extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: false
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
if (this.props.eleType === "pattern") {
this.setState({selected: true})
}
}
handleClick() {
if (!this.state.selected) {
this.setState({selected: true})
}
if (this.state.selected) {
this.setState({selected: false})
}
}
render() {
let eleNumber = NaN
if (this.props.eleType === "pattern") {
eleNumber = 1;
this.state.selected === true;
}
if (this.props.eleType === "gift") {
eleNumber = 2;
}
if (this.props.eleType === "personal") {
eleNumber = 3;
}
if (this.state.selected) {
return <div className="square-box selected"
onClick={this.handleClick}>{eleNumber}</div>
} else {
return <div className="square-box"
onClick={this.handleClick}>{eleNumber}</div>
}
}
}
class ProgressBar extends React.Component {
render() {
return <div className="d-flex custom-progress-bar">
<ProgressElement eleType="pattern"/>
<ProgressElement eleType="gift"/>
<ProgressElement eleType="personal"/>
</div>
}
}
It's working fine, but my only problem is that I don't know how to make the other boxes unselected. For that, I would need the other sibling elements to be aware of the state of the other ones. I've thought of two ways to do this but have been unable to make one of these work so far :
first one would be to pass informations about the state of the child to the parent, but I think that goes against the principle of unidirectional data flow
second one would be to put the "active" state in the parent instead of the child, but I'm not sure how to keep tracks of all child like this. I've tried to do it with an array but didn't succeed so far.
Could anyone help me with this ? I know this is probably basic stuff but I'm having a hard time with React. Thanks for reading me and thanks in advance.
You need to keep track of currently active child within the parent component and pass it down as a prop:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
class ProgressElement extends React.Component {
handleClick = eleType => this.props.onSetActive(eleType)
render(){
return (
<div
className={this.props.eleType === this.props.activeEleType && 'active'}
onClick={() => this.handleClick(this.props.eleType)}
>
{this.props.eleType}
</div>
)
}
}
class ProgressBar extends React.Component {
state = {activeEleType: null}
onSetActive = activeEleType => this.setState({
activeEleType
})
render() {
return (
<div className="progress-bar">
{
['pattern', 'gift', 'personal'].map(key => (
<ProgressElement
activeEleType={this.state.activeEleType}
eleType={key}
key={key}
onSetActive={this.onSetActive}
/>
))
}
</div>
)
}
}
render (
<ProgressBar />,
rootNode
)
.progress-bar {
display: flex;
}
.progress-bar>div {
width: 100px;
height: 100px;
background: grey;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
cursor: pointer;
}
.progress-bar>div.active {
border: 2px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Try and apply this, I did not test the code myself so make sure that you apply the logic to your code (I'm rusted with classes)
class ProgressElement extends React.Component {
render() {
if (this.props.eleType === this.props.selected) {
return <div className="square-box selected"
onClick={this.props.act({selectedValue:""})}>{this.props.eleType}</div>
} else {
return <div className="square-box"
onClick={this.props.act({selectedValue:this.props.eleType})}>{this.props.eleType}</div>
}
}
}
class ProgressBar extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedValue: ""
}
}
updateSelected = (selected) => {
this.setState(selected);
}
render() {
return <div className="d-flex custom-progress-bar">
<ProgressElement eleType="pattern" act={this.updateSelected} selected={this.state.selectedValue}/>
<ProgressElement eleType="gift" act={this.updateSelected} selected={this.state.selectedValue}/>
<ProgressElement eleType="personal" act={this.updateSelected} selected={this.state.selectedValue}/>
</div>
}
}
I am new to React and am trying to build an app in which a user can create a card, delete a card, and change the order of the cards array by clicking left or right arrow to switch elements with the element on the left or on the right.
I am struggling to code this functionaliy. I have the function written to switch the card with that on the left, but this function is not doing anything right now. I also do not get any errors in the console from this function, so I really cannot determine where I am going wrong here.
Here is the code so far:
CardList.js will display the form to add a card and display the array of CardItems, passing the functions to switch these items to the left or right ('moveLeft', 'moveRight') as props.
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index,card) {
if (index > 1) {
this.state.cards.splice(index, 1);
this.state.cards.splice((index !== 0) ? index - 1 : this.state.cards.length, 0, card)
}
return this.state.cards
}
moveRight(index, card) {
// ?
}
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft.bind(this)}
moveRight={this.moveRight.bind(this)}
/>
);
});
}
}
export default CardList;
CardItem is taking in those props and ideally handling moving the card left or right in the array once the left or right icon is clicked.
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={this.leftClick.bind(this)} style={{ color: 'blue'}}></i>
<i class="arrow right icon" onClick={this.rightClick.bind(this)} style={{ color: 'blue'}}></i>
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index, card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
Not sure where I am going wrong here. Any help would be appreciated
Edit #1
Hey guys, so I wrote out a different function to handle moving the card to the left, and I decided to bind "this" to that method in the constructor because I was getting errors saying the program could not read it. However, I am still getting errors basically saying that everything is not defined when I pass the function from CardList to CardItem as props. Does anybody know what the problem is? I suspect its my syntax when I call the methods in CardItem.
CardList.js
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
constructor(props) {
super();
this.moveLeft = this.moveLeft.bind(this);
this.moveRight = this.moveRight.bind(this);
this.state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
}
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index, card) {
this.setState((prevState, prevProps) => {
return {cards: prevState.cards.map(( c, i)=> {
// also handle case when index == 0
if (i === index) {
return prevState.cards[index - 1];
} else if (i === index - 1) {
return prevState.cards[index];
}
})};
});
}
moveRight(index, card) {
// ?
}
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft}
moveRight={this.moveRight}
/>
);
});
}
}
export default CardList;
CardItem.js
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={leftClick(index, card)} style={{ color: 'blue'}}></i>
<i class="arrow right icon" onClick={rightClick(index, card)} style={{ color: 'blue'}}></i>
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index,card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
To update state arrays in React, you shouldn't use splice, push or the [] operator.
Instead use the methods that return a new array object viz. map, filter, concat,slice.
For a detailed explanation, see this article.
So you can do something like :
moveLeft(index,card) {
this.setState((prevState, prevProps)=> {
return {cards: prevState.cards.map((c,i)=> {
// also handle case when index == 0
if(i == index) {
return prevState.cards[index-1];
} else if(i == index-1) {
return prevState.cards[index];
}
})};
});
}
When updating React state using the previous value, always use
setState((prevState,prevProps)=>{ return ...})
as such state updates may be asynchronous. See React docs.
Since you are calling the parent component method from child, it's better to bind these methods in the CardList constructor. Eg:
this.moveLeft = this.moveLeft.bind(this);
this.moveRight ....
I created a glider that works good, no problem with the functionality, what I'm trying to achieve is to add a button inside the slides. So far, the links and all the slide content works good but not the button click event. I tried adding the .disable() and the pause() methods but it doesn't work. And I can't find anything like this, nor anything in the documentation. If anyone would have an approach, it'll help me a lot.
Glide holder:
import React, { Component } from 'react';
import Glide, {Swipe, Controls} from '#glidejs/glide';
import myComponent from './myComponent';
class myHolder extends Component {
constructor(props) {
super(props);
}
}
componentDidMount() {
const glide = new Glide(`.myGliderComponent`, {
type: carouselType,
focusAt: 0,
perTouch: 1,
perView: 4,
touchRatio: 1,
startAt: 0,
rewind: false,
});
glide.mount({Controls});
const CAROUSEL_NUMBER = 3;
const carouselType = this.props.displayedProducts.length <= CAROUSEL_NUMBER ? 'slider' : 'carousel';
}
render() {
return (
<div>
<div data-glide-el="track" className="glide__track">
<ul className="glide__slides">
{
this.props.displayedProducts.map(({ name, image,} = product, index) => (
<li className="glide__slide slider__frame">
<MyComponent
name={name}
image={image}
/>
</li>
))
}
</ul>
</div>
</div>
);
}
}
export default myHolder;
myComponent:
import React from 'react';
const myComponent = (
{
name,
image,
}
) => {
const buttonClicked = () => {
console.log("button clicked")
}
return (
<div>
<p>{name}</p>
<img
alt=""
src={image}
/>
<button onClick={buttonClicked}>Testing btn</button>
</div>
);
}
export default myComponent;
For anyone trying the same, I just added position:static to the class, and the glide.mount({Anchor}); to the mount() method
I have a list of data with images. I want to make image carousel. For this I have created card component and I want here to display 4 cards at a time and remaining should be hidden. Then i want to setTimeout of 5s to display remaining but only for at a time.
So far I have done this.
about.js
import './about.scss';
import data from '../../data/data';
import CardSection from './card';
class About extends React.Component{
constructor(props){
super(props);
this.state = {
properties: data.properties,
property: data.properties[0]
}
}
nextProperty = () => {
const newIndex = this.state.property.index+4;
this.setState({
property: data.properties[newIndex]
})
}
prevProperty = () => {
const newIndex = this.state.property.index-4;
this.setState({
property: data.properties[newIndex]
})
}
render() {
const {property, properties} = this.state;
return (
<div className="section about__wrapper">
<div>
<button
onClick={() => this.nextProperty()}
disabled={property.index === data.properties.length-1}
>Next</button>
<button
onClick={() => this.prevProperty()}
disabled={property.index === 0}
>Prev</button>
<Container className="card__container">
<div class="card__main" style={{
'transform': `translateX(-${property.index*(100/properties.length)}%)`
}}>
{
this.state.properties.map(property => (
<CardSection property={property}/>
))
}
</div>
</Container>
</div>
</div>
)
}
}
export default About
about.scss
.card__container{
overflow-x: hidden;
}
.card__main{
display: flex;
position: absolute;
transition: transform 300ms cubic-bezier(0.455, 0.03, 0.515, 0.955);
.card__wrapper {
padding: 20px;
flex: 1;
min-width: 300px;
}
}
card.js
import React from "react";
import { Card, CardImg, CardText, CardBody,
CardTitle, CardSubtitle, Button } from 'reactstrap';
class CardSection extends React.Component {
render() {
return (
<div className="card__wrapper">
<Card>
<CardImg top width="100%" src={this.props.property.picture} alt="Card image cap" />
<CardBody>
<CardTitle>{this.props.property.city}</CardTitle>
<CardSubtitle>{this.props.property.address}</CardSubtitle>
<CardText>Some quick example text to build on the card title and make up the bulk of the card's content.</CardText>
<Button>Button</Button>
</CardBody>
</Card>
</div>
);
}
}
export default CardSection;
I have added transition in them to change card onclick but i want them to auto change and hide the remaining card.
Right now it looks like this,
You can add items in componentDidMount method using setInterval
componentDidMount() {
this.interval = setInterval(() => this.setState({
properties:data.properties /* add your data*/ }), 4000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
You can have a property called showCardIds that holds an array of the Id of cards that need to be shown, and use that to set a Boolean property called hidden on the div of the card.
You could also do something like this as shown in the example below, this example also uses showCardIds as a state. It filters only for the property that needs to be rendered and filters out the rest.
Here is an example:
...
{
this.state.properties.filter((property, index) => showCardIds.includes(index)).map(property => (
<CardSection property={property}/>
))
}
...
That way only the ones that are present in the array of showCardIds would show up, there needs to be more logic to be written that would populate the ids in showCardIds
Hope this helps. The hidden property is supported from HTML5, and should work on most browsers, unless they are truly "ancient".
I want to change a class on my navigation bar, so when a user scrolls it fades from transparent to opaque. From following some other questions on here and other solutions online i've come up with the below and tweaked it a few ways, but nothing seems to be working.
I added in a console log to error check but it never runs.
Page component
import React from 'react'
import Nav from './_includes/Nav.jsx'
import VideoBanner from './_includes/VideoBanner.jsx'
import Section from './_includes/Section.jsx'
import { object } from 'prop-types'
class Homepage extends React.Component {
constructor (props) {
super(props)
if (props.initialSetup) props.initialSetup()
this.state = {
navOpaque: true
}
this.handleScroll=this.handleScroll.bind(this)
}
componentDidMount () {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll () {
const { navOpaque } = this.state
const { pageYOffset } = window;
if (pageYOffset >= 10 ) {
this.setState ({
navOpaque: false
})
}
console.log('you have scrolled')
return navOpaque
}
render () {
const { navOpaque } = this.state
return (
<div className="homepageContainer">
<Nav
navOpaque={navOpaque}
/>
<VideoBanner />
<Section />
</div>
)
}
}
Homepage.propTypes = {
dataSource: object,
domainSettings: object,
pageData: object.isRequired,
}
export default Homepage
Nav component
import React from 'react'
import NavItem from './NavItem.jsx'
import { bool } from 'prop-types'
import classnames from 'classnames'
class Nav extends React.Component {
constructor (props) {
super(props)
this.state = {
activeTab: '',
highlight: false
}
this.handleClick = this.handleClick.bind(this)
}
handleClick (activeTab) {
if (!activeTab) activeTab = ''
this.setState({
activeTab
})
}
render() {
const { highlight } = this.state
const { navOpaque } = this.props
const navClass = classnames({
'opaque': navOpaque,
'navbar': true,
'navbar-default': true,
'navbar-fixed-top': true,
'hidden-print': true,
'navbar-megamenu': true
})
return (
<nav id='header' className={navClass} role='navigation'>
<div className='container'>
<div className='navbar-header'>
<button type='button' className='navbar-toggle hidden-sm hidden-md hidden-lg'>
<span className='sr-only'>Toggle navigation</span>
<span className='icon-bar' />
<span className='icon-bar' />
<span className='icon-bar' />
</button>
<a className='navbar-brand' href='/'>Brand</a>
</div>
<div className='col-md-4 collapse navbar-collapse'>
<ul className='nav navbar-nav list-inline'>
{Object.keys(navConfig).map(function (listGroup, key) {
return(
<NavItem
key={key}
listGroup={listGroup}
linkData={navConfig[listGroup]}
highlight={ navConfig[listGroup].text === 'test' ? {highlight} : null }
/>
)
})}
</ul>
</div>
</div>
</nav>
)
}
}
Nav.propTypes = {
navOpaque: bool
}
export default Nav
Create a class' attribute ref with React.CreateRef() and attach it to the element where you want to remove classes on scroll.
Then in your handleScroll method, take this.ref.current
and do whatever you want on the classList
this.ref.current.classList.remove('my-class')
Hope this help.
https://reactjs.org/docs/refs-and-the-dom.html
https://www.w3schools.com/jsref/prop_element_classlist.asp
Your code seems to be right, at least to have the console.log to be executed when you scroll.
You might try to check if your class container has a height that allows you to scroll.
Check if this example helps:
https://codesandbox.io/s/nwr99l5l8p
Note that in the styles.css, the container has a height of 200vh, allowing the scrolling.
.App {
font-family: sans-serif;
text-align: center;
height: 200vh;
}