Learning React by coding, here i'm using 'react-zoom-pan-pinch' for zoomIn/zoomOut by clicking buttons, but it is also possible to zoomIn and zoomOut using mouse scroll wheel, and i want that value is it possible ? so when i zoomIn or zoomOut using mouse scroll wheel i want that value which shows i have zoomed in or zoomed out and how much.
Demo of code :
https://codesandbox.io/s/qmn69?file=/src/index.js:139-159
code:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import styled from "styled-components";
const Container = styled.div`
height: 100vh;
width: 100vw;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
`;
const Box = styled.div`
width: 50px;
height: 50px;
background-color: ${props => (props.backColor ? props.backColor : "blue")};
margin: 5px;
`;
const Toolbox = styled.div`
display: flex;
justify-content: center;
width: 100%;
max-width: calc(100vw - 60px);
margin-bottom: 10px;
button {
margin-left: 10px;
width: 2em;
}
`;
function App() {
const onClick = () => {
alert("onClick : open popup");
};
const onContextMenu = e => {
e.preventDefault();
alert("onContextMenu : show info");
};
const renderBoxes = () => {
let i;
let arr = [];
for (i = 0; i < 30; i++) {
arr.push(
<Box key={"b" + i} onClick={onClick} onContextMenu={onContextMenu} />
);
arr.push(
<Box
key={"r" + i}
onClick={onClick}
onContextMenu={onContextMenu}
backColor="red"
/>
);
arr.push(
<Box
key={"y" + i}
onClick={onClick}
onContextMenu={onContextMenu}
backColor="yellow"
/>
);
arr.push(
<Box
key={"g" + i}
onClick={onClick}
onContextMenu={onContextMenu}
backColor="gray"
/>
);
}
return arr;
};
return (
<TransformWrapper
defaultScale={1}
defaultPositionX={200}
defaultPositionY={100}
>
{({ zoomIn, zoomOut, resetTransform, positionX, positionY, ...rest }) => (
<React.Fragment>
<Toolbox>
<button onClick={zoomIn}>+</button>
<button onClick={zoomOut}>-</button>
<button onClick={resetTransform}>x</button>
</Toolbox>
<TransformComponent>
<Container>{renderBoxes()}</Container>
</TransformComponent>
</React.Fragment>
)}
</TransformWrapper>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Related
I'm building a tic-tac-toe app. I haven't added functionality between the X's and O's, right now, I'm stuck on rending ONE image via the onClick. I've set the state to make the onClick - at least I think - and I've written out my conditional, but its rendering X images for each square. How do I render an image for only ONE square instead of them all? Here is my code:
Xs.js
import React from 'react'
import styled from 'styled-components'
import X1 from './images/X1.jpg'
import X2 from './images/X2.jpg'
import X3 from './images/X3.jpg'
const Image = styled.img`
width: 175px;
height: 175px;
`
const Xs = () => {
const X = [X1, X2, X3]
const randomXImg = Math.floor(Math.random() * X.length)
return (
<Image src={X[randomXImg]} />
)
}
export default Xs
Cell.js - this is just a div to show for the actual square
import React from 'react'
import styled from 'styled-components'
const CellBlock = styled.div`
display: flex;
align-items: center;
justify-content;
border: 1px solid white;
width: 200px;
height: 200px;
background-color: white;
`
const Container = styled.div`
display: flex;
flex-wrap: nowrap;
flex-direction: column;
align-items: center;
box-sizing: border-box;
`
const Board = styled.div`
display: grid;
grid-template-columns: 210px 210px 202px;
grid-template-rows: 210px 210px 202px;
background-color: darkgreen;
`
const Cell = ({ onClick, isTurn, children }) => {
let squares = []
squares = Array.from(Array(9).fill(''))
return (
<Container>
<Board>
{
squares.map((box, id) => (
<CellBlock
key={id}
id={id}
onClick={onClick}
isTurn={isTurn}
>
{children}
</CellBlock>
))
}
</Board>
</Container>
)
}
export default Cell
Here is the gameboard which is the parent component of both Xs.js and Cell.js
Gameboard.js
import React, { useState } from 'react'
import Cell from './Cell'
import Xs from './Xs'
const Gameboard = () => {
const [ player, setPlayer ] = useState('X')
const [ isNotOccupied, setIsNotOccupied ] = useState(false)
return (
<>
<Cell
onClick={() => setIsNotOccupied(!isNotOccupied)}
isTurn={player}
>
{ isNotOccupied && player === 'X' && <Xs />}
</Cell>
</>
)
}
export default Gameboard
Any help would be greatly appreciated.
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;
}
}
}
I'm pretty new to react, and i'm playing with a very simple site that fetches data from a pokemon api.
I wanted to add an onClick event on my list items to change the css when it's clicked. But
when i add setExpand(!expand) to my list item like this <Li onClick={setExpand(!expand)}> i get an error telling me "Error: Too many re-renders. React limits the number of renders to prevent an infinite loop" that i can't make sense of.
Main
//Styling
const Section = styled.section`
margin-top: 100px;
`;
const Ul = styled.ul`
margin:0;
padding:0;
list-style:none;
display: flex;
justify-content: center;
flex-wrap: wrap;
`;
const Main = styled.main`
max-width: 1200px;
margin: 0 auto;
width: 100%;
`;
export default function Test() {
//States
const [pokemonArray, newPokemon] = useState([]);
//Hooks
useEffect(() => {
(async () => {
const dataArray = [];
for (let i = 1; i < 6; i++) {
let data = await fetch('https://pokeapi.co/api/v2/pokemon/' + i).then(res => res.json());
dataArray.push(data);
}
newPokemon(dataArray);
})()
}, []);
//Render
return (
<Route exact path="/test">
<Main>
<Section>
<Ul>
{articleTemplate()}
</Ul>
</Section>
</Main>
</Route>
);
//Functions
function articleTemplate() {
const components = []
pokemonArray.forEach((elm, index) => {
components.push(<Li key={index} src={elm.sprites.front_default} />)
})
return components;
};
}
List item component
import React, { useState } from 'react';
import styled from 'styled-components';
const Li = styled.li`
width: 50px;
height: 50px;
margin: 25px;
box-sizing: border-box;
background:url('https://via.placeholder.com/500x500');
`;
const Img = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
`;
export default function Image(props) {
const [expand, setExpand] = useState(false);
return (
<Li onClick={setExpand(!expand)}>
<Img src={props.src} />
</Li>
)
}
You need to pass a callback instead of executing the code inside onClick
<Li onClick={() => setExpand(exp => !exp)}>
<Img src={props.src} />
</Li>
You can pass handleClick function
export default function Image(props) {
const [expand, setExpand] = useState(false);
const handleClick=()=>{
setExpand(!expand);
}
return (
<Li onClick={handleClick}>
<Img src={props.src} />
</Li>
)
}
I have built the following Carousel / Image Slider that treats the children provided to it as Slides. So far I am not using much JS, it's all pretty much HTML & CSS (apart from the styled-components and the fact the whole thing is a function)
import React, { Component, useState, useEffect } from 'react';
import styled from 'styled-components';
let Wrapper = styled.div`
width: 100%;
display: flex;
flex-direction: row;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scroll-snap-type: x mandatory;
& > div {
scroll-snap-align: start;
}
`;
let Slide = styled.div`
width: 100%;
flex-shrink: 0;
padding: 30px;
`;
let SlideLink = styled.a`
padding-right: 3px;
color: purple;
color: ${props => (props.isActive ? 'purple' : `gray`)};
`;
const Slider = ({ children }) => {
const [activeSlide, setActiveSlide] = useState(0);
useEffect(() => {
// ??
}, [activeSlide]);
return (
<>
<button onClick={() => setActiveSlide(activeSlide - 1)}>PREV</button>
{children.map((child, i) => {
return (
<SlideLink
href={`#slide-${i + 1}`}
isActive={activeSlide === i}
onClick={e => {
setActiveSlide(i);
}}
>
{i + 1}
</SlideLink>
);
})}
<Wrapper>
{children.map((child, i) => {
return <Slide id={`slide-${i + 1}`}>{child}</Slide>;
})}
</Wrapper>
<button onClick={() => setActiveSlide(activeSlide + 1)}>NEXT</button>
</>
);
};
export default Slider;
Its working quite well, but I'd also like to use the NEXT SLIDE and PREVIOUS SLIDE buttons for navigation. But I'm not sure how to do this with this setup. I would need to scroll horizontally inside the Wrapper I guess? Or could I just programmatically "jump" to certain ids, like when clicking on the <a href="#slide-1" />-tags somehow?
I'd really appreciate some advice, I'm quite stuck and feel I might have coded it in a not so optimal way?
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