Expand div in a Class component - javascript

I'm following this react-flip-toolkit tutorial in order to animate a<div> expansion in a component:
This is the tutorial code:
import React, { useState } from 'react'
import { Flipper, Flipped } from 'react-flip-toolkit'
const AnimatedSquare = () => {
const [fullScreen, setFullScreen] = useState(false)
const toggleFullScreen = () => setFullScreen(prevState => !prevState)
return (
<Flipper flipKey={fullScreen}>
<Flipped flipId="square">
<div
className={fullScreen ? 'full-screen-square' : 'square'}
onClick={toggleFullScreen}
/>
</Flipped>
</Flipper>
)
}
My project however, unlike the functional Component example above, uses Class components, like so:
class Field extends Component {
constructor(props) {
super(props);
this.state = {
players:[],
};
}
getPlayersByPosition = (players, position) => {
return players.filter((player) => player.position === position);
};
render() {
const { players } = this.props;
if(players){
return (
<div className="back">
<div className="field-wrapper" >
<Output output={this.props.strategy} />
// this is the target div I want to expand
<div className="row">
{this.getPlayersByPosition(players, 5).map((player,i) => (
<Position key={i} >{player.name}</Position>
))}
</div>
</div>
</div>
);
}else{
return null}
}
}
export default Field;
How can I declare AnimatedSquare() in my Class component and encapsulate my target <div> above within <Flipper/> and <Flipped/>?

I've converted the example to a class based component for you. You should be able to work the rest out from this example:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Flipped, Flipper } from "react-flip-toolkit";
import "./styles.css";
class AnimatedSquare extends Component {
state = {
fullScreen: false
};
toggleFullScreen() {
this.setState({ fullScreen: !this.state.fullScreen });
}
render() {
const { fullScreen } = this.state;
return (
<Flipper flipKey={fullScreen}>
<Flipped flipId="square">
<div
className={fullScreen ? "full-screen-square" : "square"}
onClick={this.toggleFullScreen.bind(this)}
/>
</Flipped>
</Flipper>
);
}
}
ReactDOM.render(<AnimatedSquare />, document.querySelector("#root"));
* {
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.square {
width: 5rem;
height: 5rem;
cursor: pointer;
background-image: linear-gradient(
45deg,
rgb(121, 113, 234),
rgb(97, 71, 182)
);
}
.full-screen-square {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
background-image: linear-gradient(
45deg,
rgb(121, 113, 234),
rgb(97, 71, 182)
);
}

Related

React.Children returns (function)string as `type` for Custom Components as Child with typescript

This may sound strange, maybe I completely get it wrong in the first place. But as I read some articles and react docs related to get the children and identify specific child via React.Component.map() however when I try this with my custom components, the child returns a stringify function as type. (I know that react does the stringify thing to prevent script injection). But I basically need to identify specific children that pass into the component and place them in the correct positions in another custom component. (materia_ui style).
<Card>
<CardTitle>
</CardTitle>
<CardContent>
</CardContent>
</Card>
The problem is I can't map passed children since the type has a string.
my environment uses
"react": "^17.0.2",
"#types/react": "^17.0.0",
"react-dom": "^17.0.2",
"#types/react-dom": "^17.0.0",
"typescript": "^4.1.2"
and this is what I have so far
type ExpandableCardProps = {
children: ReactElement<any> | ReactElement<any>[],
}
const ExpandableCard = ({children}: ExpandableCardProps) => {
React.Children.map(children, (child) => {
concole.log(child); // just can't map the child elements as described in some articales
})
// note that I need to identify the correct child to be render in correct position
render (
<div>
<div className="title-container">
// I want to render <ExpandableTitle> here
</div>
<div className="content-container">
// I want to render <ExpandableContent> here
</div>
<div className="content-other">
// may be some other elements here
</div>
</div>
);
}
export default ExpandableCardProps;
type CommonType = {
children: ReactNode;
}
export const ExpandableTitle ({children}:CommonType) => {
<div>
{children}
</div>
}
export const ExpandableContent ({children}:CommonType) => {
<div>
{children}
</div>
}
// usage
<ExpandableCard>
<ExpandableTitle>
/*{ some jsx here }*/
</ExpandableTitle>
<ExpandableContent>
/*{ some jsx here }*/
</ExpandableContent>
</ExpandableCard>
Here's what it looks like in the console
Here's an article I was referring to and which explained most closely what I need, but Can't use the pattern it explained since the type stringify thing, wonder it's with the React version or maybe as I mentioned earlier it's completely misunderstood by myself. I need some insight into this. How can I achieve something like this?
This seems to be working for me:
const ExpandableCard = ({children}) => {
const childArray = React.Children.toArray(children);
const expandableTitleIndex = childArray.findIndex(x => x.props.__TYPE === 'ExpandableTitle');
const expandableContentIndex = childArray.findIndex(x => x.props.__TYPE === 'ExpandableContent');
const additionalChildren = childArray.filter((_, index) => (index !== expandableTitleIndex && index !== expandableContentIndex));
return [childArray[expandableTitleIndex], childArray[expandableContentIndex], ...additionalChildren];
};
const App = () => {
return (
<ExpandableCard>
<div>Child 0 (div)</div>
<ExpandableContent>Child 1 (ExpandableContent)</ExpandableContent>
<ExpandableTitle>Child 2 (ExpandableTitle)</ExpandableTitle>
<div>Child 3 (div)</div>
</ExpandableCard>
);
};
const ExpandableTitle = ({children}) => (
<div>
{children}
</div>
);
ExpandableTitle.defaultProps = {
__TYPE: 'ExpandableTitle',
};
const ExpandableContent = ({children}) => (
<div>
{children}
</div>
);
ExpandableContent.defaultProps = {
__TYPE: 'ExpandableContent',
};
ReactDOM.render(<App />, document.querySelector("#app"));
Live on jsFiddle
After a few workarounds with the Neal Burns answer, I concluded with a typescript compatible solution.
I Will post it here since for someone it may be come in handy someday.
import React, { Children, ReactElement, ReactNode, useEffect, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './expandableCard.scss';
import { v4 as uuidv4 } from 'uuid'
const types = {
EXPANDABLE_CARD_HEADER: 'expandableCardTitle',
EXPANDABLE_CARD_CONTENT: 'expandableCardContent',
EXPANDABLE_CARD_FOOTER: 'expandableCardFooter',
EXPANDABLE_ITEMS: 'expandableItems',
}
type ExpandableCardProps = {
id?: string;
select?: boolean;
onSelect?: (id: string) => void;
children: ReactElement<ExpandableCardContentProps> | ReactElement<ExpandableCardContentProps>[];
}
const ExpandableCard = ({ id = uuidv4(), select = false, children, onSelect = () => { } }: ExpandableCardProps) => {
const transitionRef = useRef(null);
const [selected, setSelected] = useState(select);
const [expand, setExpand] = useState(false);
const headerElement = useRef<any>(null);
const contentElement = useRef<any>(null);
const expandableFooter = useRef<any>(null);
const expandableItems = useRef<any>(null);
const handleSelected = () => {
setSelected(!selected);
}
useEffect(() => {
if (selected) {
onSelect(id);
}
}, [id, onSelect, selected])
const handleExpand = () => {
setExpand(!expand);
}
Children.forEach(children, (child) => {
switch (child.props.__TYPE) {
case types.EXPANDABLE_CARD_HEADER:
headerElement.current = child;
break;
case types.EXPANDABLE_CARD_CONTENT:
contentElement.current = child;
break;
case types.EXPANDABLE_ITEMS:
expandableItems.current = child;
break;
case types.EXPANDABLE_CARD_FOOTER:
expandableFooter.current = child;
break;
default:
return <div></div>;
}
});
return (
<div className={`expandable-card ${selected ? 'expandable-card-selected' : ''}`}>
<div className={`expandable-card--content ${expand ? 'expandable-card--content-active' : ''}`}>
<div className="expandable-card--expand-button">
<button type="button" onClick={handleExpand}>expand</button>
</div>
{headerElement.current &&
<div className="expandable-card--header">
{headerElement.current}
</div>
}
{contentElement.current}
<div className="d-flex align-items-center mt-3">
<button
type="button"
className={`btn expandable-card--button ${selected ? 'expandable-card--button-active' : ''}`}
onClick={handleSelected}>
{selected && !}
</button>
{expandableFooter.current}
</div>
</div>
<CSSTransition
nodeRef={transitionRef}
in={expand}
timeout={500}
classNames={`expandable-card--drawer`}
mountOnEnter
unmountOnExit>
<div ref={transitionRef} className="expandable-card--drawer">
{expandableItems.current}
</div>
</CSSTransition>
</div >
);
}
type ExpandableCardContentProps = {
children: ReactNode,
__TYPE: string;
}
export const ExpandableCardHeader = ({ children }: ExpandableCardContentProps) => {
return (
<>
{children}
</>
);
}
ExpandableCardHeader.defaultProps = {
__TYPE: types.EXPANDABLE_CARD_HEADER,
}
export const ExpandableCardContent = ({ children }: ExpandableCardContentProps) => (
<>
{children}
</>
);
ExpandableCardContent.defaultProps = {
__TYPE: types.EXPANDABLE_CARD_CONTENT,
}
export const ExpandableCardFooter = ({ children }: ExpandableCardContentProps) => (
<>
{children}
</>
);
ExpandableCardFooter.defaultProps = {
__TYPE: types.EXPANDABLE_CARD_FOOTER,
}
export const ExpandableItems = ({ children }: ExpandableCardContentProps) => (
<>
{children}
</>
);
ExpandableItems.defaultProps = {
__TYPE: types.EXPANDABLE_ITEMS,
}
export default ExpandableCard;
Please note that this is the complete expandable component with animations in it
I'll put up the SCSS code also with this to be complete
.expandable-card {
display: flex;
flex-direction: column;
box-shadow: 0 0px 25px 0px rgba(0, 0, 0, 0.2);
width: 100%;
background-color: #fff;
border-radius: 14px;
position: relative;
&--expand-button {
position: absolute;
top: 10px;
right: 15px;
}
&-selected {
border-bottom: 15px solid yellow;
border-radius: 14px;
}
&--content {
padding: 18px 15px;
border-radius: 14px 14px 0 0;
transition: all 500ms ease-out;
&-active {
z-index: 1;
box-shadow: 0 7px 7px 0 rgba(0, 0, 0, 0.2);
}
}
&--drawer {
display: flex;
flex-direction: column;
width: 100%;
max-height: 0;
background-color: #fff;
padding: 18px 20px;
border-radius: 0 0 14px 14px;
overflow-x: hidden;
overflow-y: auto;
transition: all 500ms ease-out;
/* .classes for help dropdown animations */
&-enter-active {
max-height: 320px;
padding: 18px 20px;
}
&-enter-done {
max-height: 320px;
padding: 18px 20px;
}
&-exit-active {
max-height: 0;
padding: 0 20px;
}
&-exit-done {
max-height: 0;
padding: 0 20px;
}
}
&--header {
display: flex;
align-items: center;
}
&--button {
min-width: 43px;
height: 43px;
background: transparent;
border: 2px solid aqua;
box-sizing: border-box;
border-radius: 10px;
&:focus {
box-shadow: none;
}
&-active {
background-color: blue;
border: none;
}
}
}

React Context - "this" is undefined

I am using React Context in order to manage a global state.
So I have defined my Context with its Provider and its Consumer.
I have my videoplaying-context.js
import React from "react";
import { createContext } from 'react';
// set the defaults
const VideoContext = React.createContext({
videoPlaying: false,
setPlayingVideo: () => {}
});
export default VideoContext;
In my _app.js I have:
import App from 'next/app'
import { PageTransition } from 'next-page-transitions'
import VideoContext from '../components/videoplaying-context'
class MyApp extends App {
setPlayingVideo = videoPlaying => {
this.setState({ videoPlaying });
};
state = {
videoPlaying: false,
setPlayingVideo: this.setPlayingVideo
}
render() {
console.log('new _app.js defalt page');
const { Component, pageProps, router, state } = this.props
return (
<React.Fragment>
<VideoContext.Provider value={this.state}>
<PageTransition timeout={300} classNames="page-transition">
<Component {...pageProps} key={router.route} />
</PageTransition>
</VideoContext.Provider>
</React.Fragment>
)
}
}
export default MyApp
and then in one of my file I have put the Consumer:
import Layout from "../components/Layout";
import ReactPlayer from 'react-player
import VideoContext from '../components/videoplaying-context'
class Video extends React.Component {
constructor(props) {
super(props);
this.triggerVideo = this.triggerVideo.bind(this);
}
triggerVideo(event) {
console.log("click");
/* doing other stuff here... */
}
render() {
return (
<VideoContext.Consumer>
{context => (
<Layout>
<h1>Videos</h1>
<div>
<div className="group" id="process-video">
<div
className="poster-image"
onClick={() => {
this.triggerVideo.bind(this);
context.setPlayingVideo(true);
}}
/>
<ReactPlayer
url="https://vimeo.com/169599296"
width="640px"
height="640px"
config={{
vimeo: {
playerOptions: {
thumbnail_url: "http://placehold.it/640x640.jpg",
thumbnail_width: 640,
thumbnail_height: 640
}
}
}}
/>
</div>
</div>
<style jsx global>{`
.group {
position: relative;
height: 0;
overflow: hidden;
height: 640px;
width: 640px;
}
.poster-image {
background: url("http://placehold.it/640x640.jpg") center center;
background-size: cover;
bottom: 0;
left: 0;
opacity: 1;
position: absolute;
right: 0;
top: 0;
z-index: 10;
height: 640px;
width: 640px;
transition: all 0.4s ease-in;
}
.poster-image + div {
position: absolute;
top: 0;
left: 0;
width: 640px;
height: 640px;
}
.poster-image.video--fadeout {
opacity: 0;
}
`}</style>
</Layout>
)}
</VideoContext.Consumer>
);
}
}
export default Video;
So, the function "context.setPlayingVideo(true)" is working fine and it's correctly setting the global state "videoPlaying" to true, but, after the introduction of the Context, "this.triggerVideo.bind(this);" is not working anymore because "this" is undefined.
I tried removing it and other stuff but I'm really stuck and I don't know hot to fix it.
Thanks everyone!
On this line you are not calling the method triggerVideo
onClick={() => { this.triggerVideo.bind(this); context.setPlayingVideo(true); }}
Change to:
onClick={() => { this.triggerVideo(); context.setPlayingVideo(true); }}
or to:
onClick={() => { this.triggerVideo.bind(this)(); context.setPlayingVideo(true); }}

Making the entire div to behave as a checkbox using React

I am having this condition where I display a list of checkboxes . Here I want the entire div to act as a checkbox. Currently it works only on the checkbox and the label . Instead I want the entire div to be made checked/unchecked.
Help would be appreciated
Sandbox: https://codesandbox.io/s/modest-meninsky-9lcmb
Code
import React from "react";
import ReactDOM from "react-dom";
import { Checkbox } from "semantic-ui-react";
import "./styles.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{ display: "CB1", checked: false, name: "cb1" },
{ display: "CB2", checked: false, name: "cb2" },
{ display: "CB3", checked: false, name: "cb3" }
]
};
}
handleItemClick = (event, data) => {
const index = this.state.data.findIndex(item => item.name === data.name);
const optionsArr = this.state.data.map((prevState, i) =>
i === index
? {
display: prevState.display,
name: prevState.name,
checked: !prevState.checked
}
: prevState
);
this.setState({ data: optionsArr });
};
render() {
return (
<div>
<div className="menu-item-holder">
{this.state.data.map((item, i) => (
<div className="menu-item" key={i}>
<Checkbox
onChange={this.handleItemClick}
checked={item.checked}
label={item.display}
name={item.name}
/>
</div>
))}
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
styles.css
.menu-item-holder {
border: 1px solid #ccc;
width: 150px;
}
.menu-item {
padding: 5px 10px;
border-bottom: 1px solid #ccc;
cursor: pointer;
}
Just add a width to .ui.checkbox.
.ui.checkbox {
width: 100%;
}
Just increase the width of label with parent width. Add below css in styles.css,
.ui.checkbox input.hidden+label {
width:150px;
}

Modifying child last component and css attribute in ReactJs

I want to show these three images from my Parent component and i am trying to remove the line after Summary breadcrumb .
trying to remove last line
This is my root Class of parent and trying to show only three images but not the line.
This is BCrumb.css file
.root {
color: #fff;
font-size: 12px;
display: flex;
padding: 1px;
justify-content: initial;
margin-left: 1%;
}
This is BCrumb.tsx class
import * as React from "react";
import classes from "./BCrumb.css";
interface IBCrumbProps {
children?: any;
}
class BCrumb extends React.Component<IBCrumbProps, {}> {
render() {
console.log("Children>>>>"+React.Children.count(this.props.children));
return <div className={classes.root}>
{React.Children.map(this.props.children, (child , i) => {
// here i am trying to hide the line after summary but i //dont know how to implement it here
if (i == 3) return
return child
})}
</div>;
}
}
export default BCrumb;
BCItems.css file
.root {
color: #297848;
font-size: 12px;
text-align: center;
margin-left: 13%;
display: flex;
justify-content: space-evenly;
}
.step-title {
color: #297848;
font-size: 12px;
text-align: center;
}
.step-icon.active {
height: 28px;
margin-bottom: 3px;
}
div.disabled {
height: 28px;
opacity: 0.5;
pointer-events: none;
}
.stepconnector {
position: fixed;
height: 1.7px;
width: 3.6%;
margin-top: 2%;
background-color: #ccc;
margin-left: 3.6%;
display: block;
}
BCItems.tsx class
import * as React from "react";
import classes from "./BCItem.css";
import classnames from "classnames";
interface IBCItemProps{
children?: any;
active?: boolean;
inactiveSrc?: boolean;
activeSrc?: boolean;
}
class BCItems extends React.Component<IBCItemProps, {}> {
render() {
const { children, active, activeSrc, inactiveSrc, label } = this.props;
const className = classnames({
[classes.root]: true,
[classes.disabled]: !active
});
//var i = ;
return (
<div className={className}>
<div>
{active ? (
<img className={classes.img1} src={activeSrc} />
) : (
<img className={classes.img1} src={inactiveSrc} />
)}
<p className={classes.labelText}>{label}</p>
</div>
<div className={classes.stepconnector}></div>
</div>
);
}
}
export default BCItems;
This is the class that showing BCrumb items
import * as React from "react";
import BCItems from "../../components/BCrumb/BCItems";
import BCrumb from "../../components/BCrumb/BCrumb";
import Step1_1 from "../../../assets/step-1-active.png";
import Step1_0 from "../../../assets/step-1.png";
import step2_1 from "../../../assets/step-2-active.png";
import step2_0 from "../../../assets/step-2.png";
import step3_1 from "../../../assets/step-3-active.png";
import step3_0 from "../../../assets/step-3.png";
import classes from "./HomePage.css";
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = { setAct1: true, setAct2: false };
}
render() {
const styles = {
containerStyle: {
paddingLeft: 37
}
};
const { containerStyle } = styles;
return (
<div>
<BCrumb>
<BCItems
active={true}
activeSrc={Step1_1}
inactiveSrc={Step1_0}
label="Profile"
/>
<BCItems
active={true}
activeSrc={Step2_1}
inactiveSrc={Step2_0}
label="DashBoard"
/>
<BCItems
active={true}
activeSrc={Step3_1}
inactiveSrc={Step3_0}
label="Summary"
/>
</BCrumb>
</div>
);
}
}
export default HomePage;
I dont know how to hide the last item of css element (line) from the parent class using React.Children.map
Use last-child, a CSS selector:
.root:last-child .stepconnector {
display: none !important;
}

Create React List with Remove Button on Hover

I'm creating a DropDown List box and each item in the list has a remove (X) button to remove the item from the list. How is it possible to show the remove button "only" when the item is hovered over?
The current code shows the clear button each each item but I only want it to show when the item is hovered over
Sorry, here is the code
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const ListWrapper = styled.div`
position: absolute;
width: 16rem;
z-index: 1;
background: white;
&:hover {
cursor: pointer;
}
`;
const ListMenu = styled.div`
position: absolute;
width: 100%;
z-index: 1;
background: white;
overflow-x: hidden;
`;
const ListMenuHeader = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-end;
`;
const DropdownText = Text.Link.extend`
padding-top: 3rem;
`;
const DropdownButton = styled.div`
padding: 1 rem 0.75rem;
`;
const ListMenuItem = styled.div`
display: flex;
background-color: grey)};
color: grey};
>[name~=icon] {
right: 0rem;
border-radius: 0;
background: none;
align-items: right;
justify-content: right;
&:hover {
background-color: grey)};
}
&:focus {
outline: none;
}
`;
class ListListMenu extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
text: PropTypes.node.isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
component: PropTypes.func.isRequired,
selectedItem: PropTypes.any,
getItemProps: PropTypes.func.isRequired,
highlightedIndex: PropTypes.number,
closeListMenu: PropTypes.func.isRequired,
};
static defaultProps = {
selectedItem: null,
highlightedIndex: null,
}
onClearClick = (items,item1) => (item) => {
const index = items.indexOf(item1);
if (index > -1) {
items.splice(index, 1);
}
}
render() {
const {
id, text, items, component, selectedItem, getItemProps,
highlightedIndex, closeListMenu,
} = this.props;
return (
<ListWrapper id={id} >
<ListMenuHeader onClick={closeListMenu}>
<DropdownText>{text}</DropdownText>
<DropdownButton
id={`${id}-button`}
>
<Icon type="caret-up" appearance="neutral" />
</DropdownButton>
</ListMenuHeader>
<ListMenu>
{selectedItems.map((item, index) => (
<ListMenuItem
{...getItemProps({
item,
isActive: highlightedIndex === index,
isSelected: _.isEqual(selectedItem, item),
})}
key={index}
>
{React.createElement(component, { item })}
<Button // CLEAR BUTTON
name={item}
id={item}
icon="remove"
onClick={this.onClearClick(items, item)}
circle
display="flat"
appearance="disabled"
id="clear-search-button"
/>
</ListMenuItem>
))}
</ListMenu>
</ListWrapper>
);
}
}
export default ListListMenu;
Here is one way you could probably just have that "x" appear on hover.
Instead of looking for a "hover" event, what about looking for an "onmouseenter" event combined with "onmouseleave"?
Like so...
class Example extends React.Component {
onHover() {
this.refs.deleteX.style.display = "block";
}
onExit() {
this.refs.deleteX.style.display = "none";
}
render() {
return (
<div>
<input onmouseenter={ this.onHover } onmouseleave={ this.onExit } />
<p ref="deleteX">x</p>
</div>
)
}
}
Kind of like this post

Categories