How to implement drag and drop behaviour in React - javascript

I'm trying to implement drag and drop behaviour using React and react-beautiful-dnd library.
I want to chose some images using react-dropzone library and after choosing images I show thumbnails on the right side of the screen and after that I want to be able to drag one of those thumbnails to the left side and drop it to one of those containers.
My code is as follows:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import Dropzone from "react-dropzone";
import { Animated } from "react-animated-css";
import { orderBy } from "lodash";
import UUID from "uuid";
import "./styles.css";
const animationIn = "fadeInDown";
const animationOut = "fadeOutUp";
const animationDuration = 400; // in ms
const pictureTypes = [
{
title: "3/4 front left",
imageOverlay: "https://via.placeholder.com/100",
item: "front-left",
mandatory: true,
image: null,
priority: 1
},
{
title: "3/4 rear right",
imageOverlay: "https://via.placeholder.com/100",
item: "rear-right",
mandatory: true,
image: null,
priority: 2
},
{
title: "Inside door right",
imageOverlay: "https://via.placeholder.com/100",
item: "front-door-right-i",
mandatory: true,
image: null,
priority: 3
}
];
class App extends Component {
constructor(props) {
super(props);
this.state = {
files: [],
pictureTypes: pictureTypes
};
this.onDragEnd = this.onDragEnd.bind(this);
}
onDragEnd(result) {
debugger;
// dropped outside the list
if (!result.destination) {
return;
}
}
addFilesToState(files) {
let files_with_preview = [];
files.map(file => {
file["preview"] = URL.createObjectURL(file);
files_with_preview.push(file);
this.setState({ [`visibleAnimate${file.path}`]: true });
});
const new_files = [...this.state.files, ...files_with_preview];
this.setState({ files: new_files });
}
renderPreviews(files) {
if (files.length < 1)
return <div>Drag and drop some files to see them here.</div>;
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<div>Chosen pictures</div>
<div style={{ display: "flex", flexDirection: "row" }}>
{files.map((file, index) => {
return (
<Draggable key={UUID.v4()} draggableId={UUID.v4()} index={index}>
{provided => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<img
src={file.preview}
alt={file.path}
style={{ width: 80 }}
/>
</div>
)}
</Draggable>
);
})}
</div>
</div>
);
}
handlePictureTypesDrop(file, pictureType) {
const file_blob = URL.createObjectURL(file);
const updated_picture_type = {
...this.state.pictureTypes.find(pt => pt.item === pictureType.item),
image: file_blob
};
const updated_picture_types = [
...this.state.pictureTypes.filter(pt => pt.item !== pictureType.item),
updated_picture_type
];
let new_picture_types = [...updated_picture_types];
this.setState({ pictureTypes: new_picture_types });
}
renderPictureTypes() {
const { allowed_types } = this.props;
const { pictureTypes } = this.state;
const self = this;
return orderBy(pictureTypes, "priority").map(pt => {
return (
<div style={{ width: "25%", marginRight: 5 }}>
<Dropzone
onDrop={files => self.handlePictureTypesDrop(files[0], pt)}
accept={allowed_types}
multiple={false}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<div className="picture-types-wrapper">
<div>
<img
src={pt.image !== null ? pt.image : pt.imageOverlay}
alt={pt.title}
/>
</div>
<div style={{ fontSize: "0.65rem" }}>{pt.title}</div>
</div>
</div>
)}
</Dropzone>
</div>
);
});
}
// Normally you would want to split things out into separate components.
// But in this example everything is just done in one place for simplicity
render() {
const { files } = this.state;
const self = this;
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<Droppable droppableId="droppable">
{provided => (
<div
key="droppable"
{...provided.droppableProps}
ref={provided.innerRef}
>
<Dropzone
onDrop={files => self.addFilesToState(files)}
accept="image/jpeg, image/png"
>
{({ getRootProps, getInputProps }) => (
<section className="drag-drop-section">
<div {...getRootProps()}>
<input {...getInputProps()} />
<p className="drag-drop-text">
Drag 'n' drop some files here, or click to select files
</p>
</div>
</section>
)}
</Dropzone>
<div
style={{ display: "flex", flexDirection: "row", marginTop: 10 }}
>
<div style={{ display: "flex", flexDirection: "row" }}>
{self.renderPictureTypes()}
</div>
<div className="flex w-1/2 pl-2">
{self.renderPreviews(files)}
</div>
</div>
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}
}
// Put the thing into the DOM!
ReactDOM.render(<App />, document.getElementById("root"));
Here is the example code on codesandbox.
The photo's are successfully added to the right side where I render the thumbnails but I cant drag one of them then to the left side.
Any idea how to solve it?

<Draggable> should be inside <Droppable>
<Draggable /> components can be dragged around and dropped onto <Droppable />s. A <Draggable /> must always be contained within a <Droppable />. It is possible to reorder a <Draggable /> within its home <Droppable /> or move to another <Droppable />. It is possible because a <Droppable /> is free to control what it allows to be dropped on it.
see here

Related

Different tiptap editor instances getting synced even with different Y.XmlFragments

I am building a project, a collaborative board using yjs & tiptap 2 editor in React.js. I have a board with multiple sections and each section has multiple notes. Each note has its own editor instance with its own fragment. But when I add multiple notes, and type into one, other notes in the same section gets synced with the one I am typing.
Here is my Section component
export const Section: React.FC<ISectionProps> = ({ sectionData }) => {
const sectionHeaderRef = useRef<HTMLDivElement>(null);
const addNoteButtonRef = useRef<HTMLDivElement>(null);
const [maxHeight, setMaxheight] = useState("");
const [noteListMaxHeight, setNoteListMaxHeight] = useState("");
useEffect(() => {
console.log(sectionHeaderRef.current?.clientHeight);
setMaxheight(`calc(100% - ${sectionHeaderRef.current?.clientHeight ?? 0}px - 16px)`);
console.log(maxHeight);
}, [sectionHeaderRef.current]);
useEffect(() => {
console.log(addNoteButtonRef.current?.clientHeight);
setNoteListMaxHeight(`calc(100% - ${addNoteButtonRef.current?.clientHeight ?? 0}px - 16px)`);
console.log(maxHeight);
}, [addNoteButtonRef.current]);
const prepareNotesJsx = () => {
return sectionData
.toArray()
.map((note, index) => <Note options={{ bodyFragment: note.get("content") }} title={note.get("title")} />);
};
return (
<div className="bg-slate-300 max-w-sm rounded-lg p-2 flex flex-col max-h-full" style={{ minWidth: "20rem" }}>
<SectionHeader ref={sectionHeaderRef} />
<div className="flex flex-col flex-grow mt-4" style={{ maxHeight }}>
<DashedButton
ref={addNoteButtonRef}
onClick={(e) => {
const note = new Y.Map();
note.set("title", "Note");
note.set("content", new Y.XmlFragment());
sectionData.insert(0, [note]);
}}
>
<PlusCircleFilled />
<span className="ml-2 uppercase">Add note</span>
</DashedButton>
<div className="flex-grow board-section" style={{ maxHeight: noteListMaxHeight }}>
<SimpleBar style={{ height: "100%", maxHeight: "100%", marginTop: 8 }}>{prepareNotesJsx()}</SimpleBar>
</div>
</div>
</div>
);
};
And this is my note
const Note: React.FC<INoteProps> = ({ options, title }): JSX.Element => {
const { provider } = useContext(BoardContext);
const editor = useEditor({
extensions: [
StarterKit.configure({
history: false,
}),
Collaboration.configure({
// document: ydoc,
// field: id,
fragment: options.bodyFragment,
}),
CollaborationCursor.configure({
provider,
user: {
name: "user",
color: "#958DF1",
},
}),
Placeholder.configure({
placeholder: "Write something...",
}),
],
});
return (
<Card title={title}>
<EditorContent editor={editor} />
</Card>
);
};
This is what happens
The issue

React.js Image Slider Back Button

I am fairly new to JavaScript and React, so I was hoping someone would be able to help me with something that seems fairly easy, I just don't fully grasp the concepts. I do not want to use ReactRouter, just manage it within the file.
I am creating a simple image slider using React and Material UI. However, I am having some difficulty with the functionality of the back button, preventing the app from crashing when going past the first image (into the negatives), and displaying the image number (ie: 3/5).
Here is my code:
import React, { useState } from "react";
import Button from "#material-ui/core/Button";
import MobileStepper from "#material-ui/core/MobileStepper";
import Paper from "#material-ui/core/Paper";
import KeyboardArrowRight from "#material-ui/icons/KeyboardArrowRight";
import Typography from "#material-ui/core/Typography";
import { useTheme } from "#material-ui/core/styles";
import KeyboardArrowLeft from "#material-ui/icons/KeyboardArrowLeft";
const myCollection = [
{
label: "First Picture",
imgPath:
"https://randompicturegenerator.com/img/dog-generator/g0ae914387a19ba58fc07fffe7f6952176159f445a3cd128c43ad59ae8b9baed35d8784cb3cdf4f4c16897571568d60c5_640.jpg",
},
{
label: "Second Picture",
imgPath:
"https://randompicturegenerator.com/img/dog-generator/g913a2b6f81253654df3b9e66abc189e6b966daf7a7a37b814b2336ab4459a832db90c8b923a5a8e28e63bb2fdd4496e1_640.jpg",
},
{
label: "Third Picture",
imgPath:
"https://randompicturegenerator.com/img/dog-generator/g5d33df79829d9cfbba39903471d1cd1bc2d6cdd243e222607e337d5575cb76ed10582deda8ed10bb0d1c3c43a6494f5a_640.jpg",
},
{
label: "Fourth Picture",
imgPath:
"https://randompicturegenerator.com/img/dog-generator/g446bd15f34f6fcb35272fa878493a90e9600b73e57f1f1dbca68ac22b9a1f780cb58c8a940d5727f930aa78d7b537b82_640.jpg",
},
{
label: "Fifth Picture",
imgPath:
"https://randompicturegenerator.com/img/dog-generator/g2efe8ff4951d16cc2d042d3714796204a975eb2c32fc5ee60d08956a729b7d5114b32cb0db6a5ba2e6d2948b8a6a0320_640.jpg",
},
];
const App = () => {
const CollectionSize = myCollection.length;
const theme = useTheme();
const [index, setIndex] = useState(0);
const goToNextPicture = () => {
setIndex((prevIndex) => prevIndex + 1);
};
const goToPrevPicture = () => {
setIndex((prevIndex) => prevIndex - 1);
};
return (
<>
<div style={{ marginLeft: "40%" }}>
<h2>Mans Best-Friend</h2>
<div style={{ maxWidth: 400, flexGrow: 1 }}>
<Paper
square
elevation={0}
style={{
height: 50,
display: "flex",
paddingLeft: theme.spacing(4),
backgroundColor: theme.palette.background.default,
alignItems: "center",
}}
>
<Typography>{myCollection[index].label}</Typography>
</Paper>
<img
src={myCollection[index].imgPath}
style={{
height: 255,
width: "100%",
maxWidth: 400,
display: "block",
overflow: "hidden",
}}
alt={myCollection[index].label}
/>
<MobileStepper
variant="text"
position="static"
index={index}
steps={CollectionSize}
nextButton={
<Button
size="small"
onClick={goToNextPicture}
disabled={index === CollectionSize - 1}
>
Next
{theme.direction !== "rtl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</Button>
}
/>
<MobileStepper
variant="text"
position="static"
index={index}
steps={CollectionSize}
backButton={
<Button
size="small"
onClick={goToPrevPicture}
disabled={index === CollectionSize + 1}
>
Back
{theme.direction !== "ltl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</Button>
}
/>
</div>
</div>
</>
);
};
export default App;
Error I am getting in the console:
const goToNextPicture = () => {
index<{your_array}.length-1?
setIndex((prevIndex) => prevIndex + 1):setIndex(0);
};
const goToPrevPicture = () => {
index>0
setIndex((prevIndex) => prevIndex - 1):setIndex(arr.length-1);
};
enter code here

How do I set autofocus to an input that's inside a select dropdown?

I have an input field inside a select dropdown. I need to focus on that input box as soon as the dropdown is shown when the user clicks on it. I tried using refs, but that doesn't seem to work. I'm using antd 3.26.14 and React 16.8.6. Here is the code:
<Select
labelInValue
open={isOpenSelectCategory}
onDropdownVisibleChange={onDropdownVisibleChange}
placeholder="Select Category"
onChange={() => {
searchDropdownOptions('formCategories', '');
setCategoryInputValue('');
}}
notFoundContent={null}
dropdownRender={menu => (
<>
<div
onMouseDown={lockSelectCategoryClose}
onMouseUp={lockSelectCategoryClose}
style={{ marginBottom: '10px' }}
>
<Input
value={categoryInputValue}
ref={inputRef}
onChange={event => {
searchDropdownOptions(
'formCategories',
event.target.value,
);
setCategoryInputValue(event.target.value);
}}
placeholder="Search category or create new one"
/>
...
The useEffect for the Input ref:
useEffect(() => {
if (inputRef.current) inputRef.current.focus();
}, [inputRef]);
I've tried variations of the above useEffect, where instead of listening to changes on inputRef, I've listened to the change when the dropdown gets loaded. Neither of them worked though.
Any help here will be much appreciated...
Try to use ref={(input) => input && input.focus()} in combination with autofocus property e.g.:
<Input
autofocus
ref={(input) => input && input.focus()}
...
/>
Here is a working stackblitz for a react class component.
NOTE: The issue with this solution is that it focuses input on any re-render (which might not be desired).
See also how-to-set-focus-on-an-input-field-after-rendering for more options.
Code for the linked complete example:
import React, { useFocus } from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Select, Divider, Input } from 'antd';
import { PlusOutlined } from '#ant-design/icons';
const { Option } = Select;
let index = 0;
class App extends React.Component {
state = {
items: ['jack', 'lucy'],
name: '',
};
onNameChange = (event) => {
this.setState({
name: event.target.value,
});
};
addItem = () => {
console.log('addItem');
const { items, name } = this.state;
this.setState({
items: [...items, name || `New item ${index++}`],
name: '',
});
};
render() {
// const [inputRef, setInputFocus] = useFocus();
const { items, name } = this.state;
return (
<Select
style={{ width: 240 }}
placeholder="custom dropdown render"
dropdownRender={(menu) => (
<div>
{menu}
<Divider style={{ margin: '4px 0' }} />
<div style={{ display: 'flex', flexWrap: 'nowrap', padding: 8 }}>
<Input
autofocus
ref={(input) => input && input.focus()}
style={{ flex: 'auto' }}
value={name}
onChange={this.onNameChange}
/>
<a
style={{
flex: 'none',
padding: '8px',
display: 'block',
cursor: 'pointer',
}}
onClick={this.addItem}
>
<PlusOutlined /> Add item
</a>
</div>
</div>
)}
>
{items.map((item) => (
<Option key={item}>{item}</Option>
))}
</Select>
);
}
}
ReactDOM.render(<App />, document.getElementById('container'));

How to add a file and its description to the state using dropzone in ReactJs?

How can I add to the state of a component one or more files and their description, using a select, using react-dropzone.
I am using Reactjs, dropzone and bootstrap and what I want to achieve is: add one or more files (by dragging them to an area) and then see a list of the added files and a select input for each one (with options for the user to define the "type") save all of this in a state and then send that information to an API.
Something similar to what appears in the image:
The code that I have so far, returns me a list of the files that are accepted, depending on their extension (pdf, xlsx ...) and the rejected files, but I don't know how to add a select (with options of "type "from file that can be" summary "," report "," test "...) and save it in a state and then send it to an API.
The code I have so far, using react-dropzone, is this:
const baseStyle = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "20px",
borderWidth: 2,
borderRadius: 20,
borderColor: "#26C2E7",
borderStyle: "dashed",
backgroundColor: "#fafafa",
color: "#c4c4c4",
outline: "none",
transition: "border .24s ease-in-out"
};
const activeStyle = {
borderColor: "#f2f"
};
const acceptStyle = {
borderColor: "#f8f"
};
const rejectStyle = {
borderColor: "#f2f"
};
function InputFiles(props) {
const {
acceptedFiles,
fileRejections,
isDragActive,
isDragAccept,
isDragReject,
getRootProps,
getInputProps
} = reactDropzone.useDropzone({
accept: ".xlsx,.docx,.pdf"
});
const style = React.useMemo(
() => ({
...baseStyle,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {})
}),
[isDragActive, isDragReject, isDragAccept]
);
const acceptedFileItems = acceptedFiles.map((file) => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
const fileRejectionItems = fileRejections.map(({ file, errors }) => (
<li key={file.path}>
{file.path} - {file.size} bytes
<ul>
{errors.map((e) => (
<li key={e.code}>{e.message}</li>
))}
</ul>
</li>
));
return (
<section className="container">
{/* <div {...getRootProps({ style })}> */}
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
<em>(Only *.pdf , *.xlsx , *.docx files will be accepted)</em>
</div>
<aside>
<h4>Accepted files</h4>
<ul>{acceptedFileItems}</ul>
<h4>Rejected files</h4>
<ul>{fileRejectionItems}</ul>
</aside>
</section>
);
}
ReactDOM.render(<InputFiles />, document.body);
window.onload = function() {
console.log('onload');
}
<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/prop-types/15.7.2/prop-types.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dropzone/11.2.0/index.js"></script>
The goal would be to get something like this:
When adding the files and their description, they must be saved in the component's state, with the objective that when clicking on save, a POST request is made to the API and when clicking on cancel, the state information must be deleted
Just as #Emmanuel mention in his answer, you can store the files in a state, this implementation store the files in a map using the name as the key.
import React, { useMemo, useState } from "react";
import { useDropzone } from "react-dropzone";
const baseStyle = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "20px",
borderWidth: 2,
borderRadius: 20,
borderColor: "#26C2E7",
borderStyle: "dashed",
backgroundColor: "#fafafa",
color: "#c4c4c4",
outline: "none",
transition: "border .24s ease-in-out"
};
const activeStyle = {
borderColor: "#f2f"
};
const acceptStyle = {
borderColor: "#f8f"
};
const rejectStyle = {
borderColor: "#f2f"
};
function InputFiles(props) {
const [files, setFiles] = useState({});
const {
fileRejections,
isDragActive,
isDragAccept,
isDragReject,
getRootProps,
getInputProps
} = useDropzone({
onDrop: (acceptedFiles) => {
setFiles((prevFiles) =>
acceptedFiles.reduce(
(acc, file) => ({
...acc,
[file.name]: {
file,
fileType: ""
}
}),
prevFiles
)
);
},
accept: ".xlsx,.docx,.pdf"
});
const style = useMemo(
() => ({
...baseStyle,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {})
}),
[isDragActive, isDragReject, isDragAccept]
);
const acceptedFileItems = Object.keys(files).map((fileName) => {
const currentFile = files[fileName].file;
const onSelectChange = (e) => {
e.persist();
setFiles((prevFiles) => {
return {
...prevFiles,
[fileName]: {
...prevFiles[fileName],
fileType: e.target.value
}
};
});
};
return (
<li key={fileName}>
<div style={{ display: "flex" }}>
<span>
{currentFile.path} - {currentFile.size} bytes
</span>
<select value={currentFile.fileType} onChange={onSelectChange}>
<option value=""></option>
<option value="summary">summary</option>
<option value="description">report</option>
<option value="report">description</option>
</select>
</div>
</li>
);
});
const fileRejectionItems = fileRejections.map(({ file, errors }) => (
<li key={file.path}>
{file.path} - {file.size} bytes
<ul>
{errors.map((e) => (
<li key={e.code}>{e.message}</li>
))}
</ul>
</li>
));
return (
<section className="container">
{/* <div {...getRootProps({ style })}> */}
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
<em>(Only *.pdf , *.xlsx , *.docx files will be accepted)</em>
</div>
<aside>
<h4>Accepted files</h4>
<ul>{acceptedFileItems}</ul>
<h4>Rejected files</h4>
<ul>{fileRejectionItems}</ul>
<button onClick={() => console.log(files)}>console log files</button>
</aside>
</section>
);
}
export default InputFiles;
You can check it out working here https://codesandbox.io/s/react-drop-zone-select-vhc2l
useDropZone uses a callback onDrop which can let you get the names of the files like so :
const onDrop = React.useCallback((acceptedFiles) => {
// Do something with the files
}, []);
const { ... } = useDropzone({ onDrop });
Once you have the names of your files each time one is dropped, you can store it in a state, display names and selects and update that state accordingly each time a select is clicked.
Note also that the doc says it should be at least React 16.8 but it seems you use React 16.6.

need to create a new hidden div that shows after a onclick

I have a component that displays experiences. I want to create the option of showing a box ontop of my experiences with the different options they can do when they click an experience. I have different options for each experience.
This is my experiences
and I want to create a box like this with the corresponding images of each experience.
This is my experience component
import React, { memo, useCallback } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/styles';
import Typography from '#material-ui/core/Typography';
import clsx from 'clsx';
import Popper from '#material-ui/core/Popper';
import gastronomia from 'assets/experiences/gastronomia.jpg';
import productos from 'assets/experiences/productos.jpg';
import giftcard from 'assets/experiences/giftcard.jpg';
import diversion from 'assets/experiences/diversion.jpg';
import deporte from 'assets/experiences/deporte.jpg';
import belleza from 'assets/experiences/belleza.jpg';
import gastronomiaExperiences from 'data/gastronomia';
import productosExperiences from 'data/productos';
import giftcardExperiences from 'data/giftcard';
import diversionExperiences from 'data/diversion';
import deporteExperiences from 'data/deporte';
import bellezaExperiences from 'data/belleza';
// Proptypes definitions to the component.
const propTypes = {
/** Custom root className. */
className: PropTypes.string,
};
// Default props definitions.
const defaultProps = {
className: null,
};
// Component's styles
const useStyles = makeStyles(theme => ({
root: {
display: 'block',
margin: '0 auto',
maxWidth: '50%',
[theme.breakpoints.down('md')]: {
maxWidth: '70%',
},
[theme.breakpoints.down('sm')]: {
maxWidth: '100%',
},
'& .experiences-column': {
display: 'inline-block',
verticalAlign: 'top',
textAlign: 'center',
'&.col1': {
width: '36.31%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'&.col2': {
width: '63.69%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'& .experience': {
padding: 2,
position: 'relative',
'& img': {
width: '100%',
display: 'block',
},
'& .experience-title': {
position: 'absolute',
bottom: 30,
left: 0,
right: 0,
textAlign: 'center',
},
},
},
},
}), { name: 'ExperiencesStyle' });
/**
* Component used to render a grid of experiences.
*
* #param {object} props - The component's props.
* #returns {object} React element.
*/
const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = event => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
const experience = useCallback((img, title) => (
<div className="experience" aria-describedby={id} onClick={handleClick} onClick={() => setAnchorEl(!anchorEl)}>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
</div>
), []);
return (
<div className={clsx(classes.root, className)}>
<div className="experiences-column col1">
{experience(gastronomia, 'GASTRONOMÍA')}
{experience(giftcard, 'GIFT CARD')}
{experience(deporte, 'DEPORTE')}
</div>
<div className="experiences-column col2">
{experience(productos, 'PRODUCTOS')}
{experience(diversion, 'DIVERSIÓN')}
{experience(belleza, 'BELLEZA')}
</div>
<Popper id={id} open={open} anchorEl={anchorEl}>
<div className={classes.paper}>
<p>Array of images here depending on category </p>
</div>
</Popper>
</div>
);
},
);
// Component proptypes.
Experiences.propTypes = propTypes;
// Component default props.
Experiences.defaultProps = defaultProps;
export default Experiences;
I am complete lost on how to start this task. I am new to react
Ok, so
Delete experience from Experiences.
Add array of object - of your experiences
const iterateExperience = [
{img: gastronomia, title: 'GASTRONOMIA' },
{img: productos, title: 'PRODUCTOS' },
];
Later map it like this:
<div className={clsx(classes.root, className)}>
{iterateExperience.map((experience, index) => (
<div key = {index}>
<Experience img = {experience.img} title = {experience.title}/>
</div>
))}
</div>
Create Child Component - Experience:
const Experience = (props) => {
const [clicked, setClicked] = useState(true)
return (
<div onClick={() => setClicked(!clicked)}
className={clicked ? 'experience' : 'experience-clicked'}
>
<img
data-sizes="auto"
className="lazyload"
data-src={props.img}
alt={props.title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{props.title}
</Typography>
</div>
</div>
);
}
export default Experience
And if You wan't render the different component based on click, just add in Experience (You can also apply another style - whatever you want)
const Experience = (props) => {
const [clicked, setClicked] = useState(true)
return (
<div onClick={() => setClicked(!clicked)} className='experience'>
{clicked ?
<div>
<img
data-sizes="auto"
className="lazyload"
data-src={props.img}
alt={props.title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{props.title}
</Typography>
</div>
</div>
:
<p>Another Component</p>}
</div>
);
}

Categories

Resources