Simple drag and drop with sorting without external library - reactjs

I want to implement drag and drop of elements with sorting and I don't want to use any external library.
There will be 2 containers, I want to sort the elements within a container and drag/drop between the two containers. Any suggestions?
I've tried HTML5 and other things but didn't worked

In the above screen shot, I want to drag and drop the elements from shown to hidden and vice-versa, also sorting the elements among "Shown" row

/**
* Displays Dialog for Table show/hide columns
* #default {string} submitLabel, cancelLabel
* #default {bool} open
* #default {array} suggestions
*/
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import SettingsIcon from "#material-ui/icons/Settings";
import IconButton from "#material-ui/core/IconButton";
import { theme } from "../../../../utilities/theme-default";
import HLTooltip from "../../../atoms/ToolTip";
import HLDialog from "../../../grouped/Dialog";
import HLSaveSettings from "../SaveTableSettings";
import HLTypography from "../../../atoms/Typography";
import HLChip from "#material-ui/core/Chip";
import * as colors from "../../../../utilities/color";
import Draggable from "react-draggable";
export const styles = () => ({
tableAction: {
width: "100%",
marginTop: `${theme.spacing.unit}px`
},
container: {
display: "flex"
},
title: {
flex: "0 0 auto"
},
section: {
border: `1px solid ${colors.neutralDark["120"]}`,
marginTop: `${theme.spacing.unit * 3}px`,
padding: `${theme.spacing.unit}px 0 ${theme.spacing.unit * 3}px ${theme
.spacing.unit * 6}px`,
display: "flex"
},
chipTextStyle: {
marginTop: `${theme.spacing.unit * 4}px`
},
chipStyle: {
padding: `${theme.spacing.unit * 3}px`,
marginTop: `${theme.spacing.unit}px`
},
dialogStyle: {
width: "500px"
},
defaultSection: {
marginTop: `${theme.spacing.unit * 2}px`
}
});
/**
* state component which handles table show/hide columns
*/
export class HLShowHide extends React.Component {
state = {
open: false,
headers: this.props.headers
};
/**
* function to display dialog for show/hide opetations
*/
dialogToggle = () => {
this.setState({
open: !this.state.open
});
};
onStart() {
this.setState({ activeDrags: this.state.activeDrags + 1 });
}
onStop() {
this.setState({ activeDrags: this.state.activeDrags - 1 });
}
onDragStart = (e, sectionIndex, index) => {
e.stopPropagation();
this.draggedItem = this.state.headers.sections[sectionIndex].items[index];
this.dragItemSection = sectionIndex;
this.dragItemIndex = index;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/html", e.target.parentNode);
e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
};
onDragOver = (sectionIndex, index) => {
const draggedOverItem = this.state.headers.sections[sectionIndex].items[
index
];
// if the item is dragged over itself, ignore
if (this.draggedItem === draggedOverItem) {
return;
}
if (this.dragItemSection !== sectionIndex) {
var otherItems = this.state.headers.sections[this.dragItemSection].items;
otherItems.splice(this.dragItemIndex, 1);
}
// filter out the currently dragged item
let items = this.state.headers.sections[sectionIndex].items.filter(
item => item !== this.draggedItem
);
// add the dragged item after the dragged over item
items.splice(index, 0, this.draggedItem);
const sections = this.state.headers.sections;
sections[sectionIndex].items = items;
sections[this.dragItemSection].items = otherItems;
this.setState({ headers: { ...this.state.headers, sections } });
};
onDragOverSection = sectionIndex => {
if (this.state.headers.sections[sectionIndex].length === 0) {
var otherItems = this.state.headers.sections[this.dragItemSection].items;
otherItems.splice(this.dragItemIndex, 1);
let items = this.state.headers.sections[sectionIndex].items;
items.push(this.draggedItem);
const sections = this.state.headers.sections;
sections[this.dragItemSection].items = otherItems;
sections[sectionIndex].items = items;
this.setState({ headers: { ...this.state.headers, sections } });
}
};
onDragEnd = () => {
this.draggedIdx = null;
};
render() {
const { classes, submitLabel, cancelLabel } = this.props;
const { open, headers } = this.state;
const dragHandlers = {
onStart: this.onStart.bind(this),
onStop: this.onStop.bind(this)
};
const dialogBody = (
<div className={classes.dialogStyle}>
{headers.sections.map((section, sectionIndex) => (
<div className={classes.section}>
<span className={classes.chipTextStyle}>
<HLTypography
variant={
theme.palette.tableConstant.showHideColumn.sections.variant
}
color={
theme.palette.tableConstant.showHideColumn.sections.color
}
>
{section.label}
</HLTypography>
</span>
<span onDragOver={() => this.onDragOverSection(sectionIndex)}>
{section.items.map((item, itemIndex) => (
<div
className={classes.chipStyle}
key={item.value}
data-id={`${itemIndex}_${sectionIndex}`}
onDragOver={() => this.onDragOver(sectionIndex, itemIndex)}
>
<Draggable
onDrag={this.handleDrag.bind(this)}
{...dragHandlers}
>
<HLChip label={item.label} />
</Draggable>
</div>
))}
</span>
</div>
))}
<div className={classes.defaultSection}>
<HLSaveSettings
defaultLabel={headers.defaultLabel}
isDefault={headers.isDefault}
/>
</div>
</div>
);
return (
<React.Fragment>
{open && (
<div className={classes.container}>
<div className={classes.tableAction}>
<HLDialog
open={open}
submitLabel={submitLabel}
cancelLabel={cancelLabel}
bodyText={dialogBody}
maxWidth="xl"
onSubmit={() => {}}
onCancel={() => {
this.dialogToggle();
}}
/>
</div>
</div>
)}
{!open && (
<React.Fragment>
<HLTooltip title="Settings" placement="top">
<IconButton aria-label="Settings">
<SettingsIcon onClick={this.dialogToggle} />
</IconButton>
</HLTooltip>
</React.Fragment>
)}
</React.Fragment>
);
}
}
HLShowHide.propTypes = {
classes: PropTypes.object,
headers: PropTypes.arrayOf().isRequired,
open: PropTypes.bool,
submitLabel: PropTypes.string,
cancelLabel: PropTypes.string
};
HLShowHide.defaultProps = {
open: false,
submitLabel: "Set",
cancelLabel: "Cancel"
};
export default withStyles(styles)(HLShowHide);
my code which I tried

You can assign onDrop and onDrag events to div. You can hold each component in an array and when you drag you can remove the item from and array and when you drop, you can add this item to array. After that you can sort the array by array.sort()
<div onDrag={this.handleDrag()} onDrop={thishandleDrop()}>
handleDrag = () => {
//remove your item in array here
}
handleDrag = () => {
//add your item to array here
}

Related

onRemove Upload Image antDesign + delete item from interface

I have a project, and this project contains several interfaces, and among these interfaces there is an interface for uploading an image, and the problem is in the deletion icon. When you click on it, a modal appears, but the element is deleted before the modal appears.
How can i solve the problem?
this file display a list of instructions that contains upload Image
import '../../../styles/input/index.scss';
import '../../../styles/dropzone/index.scss';
import { Button, Col, message, Modal, Row, Spin, Upload, UploadFile } from 'antd';
import { FunctionComponent, useCallback, useRef, useState } from 'react';
import { motion, useAnimation } from 'framer-motion';
import { defaultTranstion } from '../../../constants/framer';
import { Controller } from 'react-hook-form';
import FromElemnetWrapper from '../form-element-wrapper';
import { Delete, UploadCloud } from 'react-feather';
import { getBase64 } from '../../../utils/get-base64';
import _ from 'lodash';
import config from '../../../api/nuclearMedicineApi/config';
import { FormattedMessage } from 'react-intl';
import BasicModal from '../modal';
import { UploadOutlined } from '#ant-design/icons';
import axios from 'axios';
import { IFormError } from '../general-form-containner';
interface DropzoneProps {
name: string;
control: any;
rules?: any;
label: string;
disabled?: boolean;
multiple?: boolean;
accept?: string;
refType?: number;
defaultFileList?: any;
onRemove?: any;
customRequest?: (option: any) => void;
onProgress?: any;
}
const Dropzone: FunctionComponent<DropzoneProps> = ({
name,
control,
rules,
label,
disabled,
multiple,
accept,
refType,
defaultFileList,
onRemove,
customRequest,
onProgress
}) => {
const focusController = useAnimation();
const errorController = useAnimation();
const [previewVisible, setpreviewVisible] = useState(false);
const [previewImage, setpreviewImage] = useState('');
const handleCancel = () => setpreviewVisible(false);
const handlePreview = async (file: any) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setpreviewImage(file?.preview ?? file.url);
setpreviewVisible(true);
};
const [isModalOpen, setIsModalOpen] = useState(false);
const [errors, setErrors] = useState<IFormError[]>([]);
const [visibleModal, setVisibleModal] = useState(false);
const [removePromise, setRemovePromise] = useState();
const [deleteVisible, setdeleteVisible] = useState<boolean>(false);
const onDeleteHandle = () => {
setdeleteVisible(false);
};
const deletehandleCancel = () => {
setdeleteVisible(false);
};
let resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
// let resolvePromiseRef = useRef<HTMLInputElement | null>(null)
const handleRemove = useCallback(() =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}, [])
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false);
setVisibleModal(false)
}
}, [removePromise]);
return (
<>
<FromElemnetWrapper
focusController={focusController}
errorController={errorController}
label={label}
required={rules.required?.value}
>
<Controller
control={control}
name={name}
rules={rules}
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, error },
}) => {
if (invalid) {
errorController.start({ scale: 80 });
} else {
errorController.start(
{ scale: 0 },
{ ease: defaultTranstion.ease.reverse() },
);
}
return (
<div
onBlur={() => {
onBlur();
focusController.start({ scale: 0 });
}}
onFocus={() => {
focusController.start({ scale: 80 });
}}
className='relative'
>
<div className='upload-container'>
<form
className='dropzone needsclick'
id='demo-upload'
action='/upload'
>
{/* <> */}
<Upload
action={`${config.baseUrl}api/services/app/Attachment/Upload`}
headers={config.headers}
ref={ref}
multiple={multiple}
disabled={disabled}
data={{ RefType: refType }}
listType='picture'
fileList={value}
id={name}
accept={accept}
onPreview={handlePreview}
onRemove={handleRemove}
iconRender={
() => {
return <Spin style={{ marginBottom: '12px', paddingBottom: '12px' }}></Spin>
}
}
progress={{
strokeWidth: 3,
strokeColor: {
"0%": "#f0f",
"100%": "#ff0"
},
style: { top: 12 }
}}
beforeUpload={
(file) => {
console.log({ file });
return true
}
}
// onProgress= {(event: any) => (event.loaded / event.total) * 100}
// onChange={(e) =>
// onChange(e.fileList)
// }
// onChange={(response) => {
// console.log('response: ', response);
// if (response.file.status !== 'uploading') {
// console.log(response.file, response.fileList);
// }
// if (response.file.status === 'done') {
// message.success(`${response.file.name}
// file uploaded successfully`);
// } else if (response.file.status === 'error') {
// message.error(`${response.file.name}
// file upload failed.`);
// }
// else if (response.file.status === 'removed') {
// message.error(`${response.file.name}
// file upload removed.`);
// }
// }}
>
<div className='upload-button'>
<div className='wrapper'>
<motion.div
className='fas fa-angle-double-up'
whileHover={{
y: [
0, -2, 2,
0,
],
transition: {
duration: 1.5,
ease: 'easeInOut',
yoyo: Infinity,
},
}}
>
<UploadCloud
style={{
margin: '.2rem',
display:
'inline-block',
}}
color='white'
size={20}
/>
Upload
</motion.div>
</div>
</div>
</Upload>
{/*
<Modal
title="Are you sure?"
visible={visibleModal}
onOk={handleOkModalRemove}
onCancel={handleCancelModalRemove}
/> */}
<BasicModal
header={
<>
<FormattedMessage id={'confirmdeletion'} />
</>
}
headerType='error'
content={
<>
<Row>
<Col span={8} offset={4}>
<Button
type='primary'
className='savebtn'
onClick={onDeleteHandle}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'affirmation'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={deletehandleCancel}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
</>
}
isOpen={visibleModal}
footer={false}
width='35vw'
handleCancel={handleCancelModalRemove}
handleOk={handleOkModalRemove}
/>
{/* {_.isEmpty(value) && (
<div className='dz-message needsclick'>
<FormattedMessage id='dropfileshere' />
</div>
)} */}
<BasicModal
isOpen={previewVisible}
header={<FormattedMessage id="Preview image" />}
footer={false}
handleCancel={handleCancel}
content={<img
alt='example'
style={{ width: '100%' }}
src={previewImage}
/>}
/>
{/* </> */}
</form>
</div>
{invalid && (
<p className='form-element-error'>
{error?.message}
</p>
)}
</div>
);
}}
/>
</FromElemnetWrapper>
</>
);
};
export default Dropzone;
Why it doesn't work?
https://ant.design/components/upload
onRemove - A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is false or a Promise which resolve(false) or reject
You have to return a promise which resolves to false or return false
How to solve it?
You have to return a promise so first of all, you need a ref or a state to store resolve function of that promise so you can call it in modal
const resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
Then you will need to assign the resolve function to the ref and return the promise
Now onRemove will wait for your promise to resolve
const handleRemove = () =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}
Now your functions handlers
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false)
}
}, [removePromise]);
You can also use the state, but I recommend ref because it doesn't rerender the component when changed.

How can I reset a dragged event to its original position on some condition in fullcallendar

I am working on fullCalendar. I want to implement something like this:
After dragging and dropping an event, a popup shows
If we cancel the event it needs to go back to its previous position
But the issue I am facing is that I am not able to find a way to return event back to its original position after dragging.
I am thinking to store the start dragging position and store also store the drop position. But I don't know if it is the right why to do that. Is there is any built in feature to do that or set the event back?
import React, { useState, useRef, useEffect } from "react";
import FullCalendar from "#fullcalendar/react"; // must go before plugins
import dayGridPlugin from "#fullcalendar/daygrid"; // a plugin!
import interactionPlugin, { Draggable } from "#fullcalendar/interaction"; // needed for dayClick
import timeGridPlugin from "#fullcalendar/timegrid";
import { resources } from "./records/resources";
import { events } from "./records/events";
import { Col, Row } from "react-bootstrap";
import "#fullcalendar/daygrid/main.css";
import "#fullcalendar/timegrid/main.css";
import "bootstrap/dist/css/bootstrap.css";
import { AlertBox } from "./atertBox";
import resourceTimelinePlugin from "#fullcalendar/resource-timeline";
function App() {
const [tableState, setTableState] = useState(events);
const [show, setShow] = useState(false);
const [showModal, setShowModal] = useState(false);
const dateRef = useRef();
const addEvent = (e) => {
const newEvent = {
id: tableState.length + 1,
title: e.target.title.value,
start: e.target.startdate.value + ":00+00:00",
end: e.target.enddate.value + ":00+00:00",
resourceId: "d",
// resourceEditable: false, to Preventing shifting between resources
};
setTableState((prev) => {
console.log(...prev);
console.log();
console.log(newEvent);
return [...prev, newEvent];
});
e.preventDefault();
};
const handleDateClick = (arg) => {
console.log("Arg", arg.dateStr);
};
const saveRecord = (record) => {
var today = new Date(record);
// var dd = String(today.getDate()).padStart(2, "0");
// var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
// var yyyy = today.getFullYear();
// today = yyyy + "-" + mm + "-" + dd;
console.log(today, "today");
dateRef.current = { today };
setShow(true);
};
const injectwithButton = (args) => {
return (
<div>
<button onClick={() => saveRecord(args.date)}>
{args.dayNumberText}
</button>
</div>
);
};
useEffect(() => {
let draggableEl = document.getElementById("external-events");
new Draggable(draggableEl, {
itemSelector: ".fc-event",
minDistance: 5,
eventData: function (eventEl) {
console.log("drag element ", eventEl);
let title = eventEl.getAttribute("title");
let id = eventEl.getAttribute("data");
return {
title: title,
id: id,
};
},
});
}, []);
const eventClicked = (event) => {
console.log("event",event)
};
return (
<div className="animated fadeIn p-4 demo-app">
<Row>
<Col lg={3} sm={3} md={3}>
<div
id="external-events"
style={{
padding: "10px",
width: "20%",
height: "auto",
}}
>
<p align="center">
<strong> Events</strong>
</p>
{events.map((event, index) => (
<div
className="fc-event"
title={event.title}
data={event.id}
key={index}
>
{event.title}
</div>
))}
</div>
</Col>
<Col lg={9} sm={9} md={9}>
<div className="demo-app-calendar" id="mycalendartest">
<FullCalendar
plugins={[
dayGridPlugin,
timeGridPlugin,
interactionPlugin,
resourceTimelinePlugin,
]}
initialView="resourceTimeline"
headerToolbar={{
left: "today,prev,next",
center: "title",
right:
"new dayGridMonth,timeGridWeek,timeGridDay,resourceTimeline",
}}
customButtons={{
new: {
text: "add new event",
click: (e) => setShow(true),
},
}}
eventColor="grey"
nowIndicator={true}
events={tableState}
resources={resources}
dateClick={handleDateClick}
eventClick={eventClicked}
editable={true}
eventDragStart={(e) => console.log("ee", e)}
eventDrop={(drop) => {
console.log("drop", drop.oldEvent._instance.range);
console.log("drop", drop.oldEvent._def.publicId);
}}
eventResizableFromStart={true}
eventResize={(resize) => console.log("resize", resize)}
dayCellContent={injectwithButton}
schedulerLicenseKey="CC-Attribution-NonCommercial-NoDerivatives"
droppable={true}
// eventReceive={(e) => {
// console.log("receive", e);
// console.log("receive", e.event._instance.range);
// }}
drop={(drop) => {
console.log("external drop", drop);
}}
/>
</div>
</Col>
</Row>
</div>
);
}
export default App;
If you're talking about dragging an existing calendar event to a new position, then eventDrop provides a revert callback which, if called, will send the event back to its previous position.
If you're talking about dragging an external event onto the calendar, eventReceive provides a revert callback which, if called, will reverse the action and the event will not be dropped onto the calendar.

set function does not trigger an update in React with Recoil

This seems like it should be easy but why isn't a button callback function with a setState call not triggering a refresh of the data item? Actually it's just the computeSMA button that isn't changing the sma when the button is selected. The other two callbacks to set inputs work. The fetchData updates the charts so i can't figure this out!! Must be too tired ...
import React, { useState, useEffect } from "react"
import { useRecoilState } from "recoil";
import { closingDataAtom, metaDataAtom, tickerAtom, timeSeriesAtom , smaAtom} from '../../utils/atoms'
import { Container } from '#material-ui/core'
import '../../utils/Home.css'
import { VictoryChart, VictoryBar, VictoryTheme, VictoryVoronoiContainer, VictoryLine, VictoryBrushContainer, VictoryZoomContainer } from 'victory';
import { Chart, Axis, Tooltip, Line, Point } from "bizcharts";
import {XYPlot, LineSeries} from 'react-vis';
const APIKEY = 'demo'
const Home = () => {
const [apikey, setApiKey] = useState(APIKEY)
const [ticker, setTicker] = useRecoilState(tickerAtom);
const [metadata, setMetaData] = useRecoilState(metaDataAtom)
const [closingdata, setClosingData] = useRecoilState(closingDataAtom)
const [dates, setDates] = useRecoilState(timeSeriesAtom)
const [sma, setSMA] = useRecoilState(smaAtom)
const TIME_RESOLUTION = 'Daily'
var requestUrl
if (TIME_RESOLUTION === 'Daily') {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey
} else {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey;
}
const fetchData = async () => {
fetch(requestUrl)
.then(response => response.json())
.then(data => {
var closing_price = []
var metadata = []
var dat = []
Object.keys(data['Time Series (Daily)']).forEach((dateKey) => {
closing_price.push(data['Time Series (Daily)'][dateKey]['5. adjusted close'])
dat.push({ 'date': new Date(dateKey) })
})
Object.keys(data['Meta Data']).forEach((metaKey) => {
metadata.push(data['Meta Data'][metaKey])
})
setDates(dat.reverse())
setClosingData(closing_price.reverse())
setMetaData(metadata)
})
.catch(e => {
});
};
const handleSMACompute = (e) => {
var sm = ['2', '3', '4']
setSMA(sm) <====== REACT IS NOT "REACTING"
}
const handleTickerInput = (e) => {
setTicker(e.target.value)
}
const handleAPIInput = (e) => {
setApiKey(e.target.value)
}
return (
<>
<Container className="container" maxWidth="sm">
<div>
<label>Ticker:</label> {ticker}
<input type="text" name="ticker" onChange={handleTickerInput} />
</div>
<div>
<label>APIKEY:</label> {apikey}
<input type="text" name="apikey" onChange={handleAPIInput} />
</div>
<button onClick={fetchData}>
Click it
</button>
<Container className="container" maxWidth="sm">
<ul>{metadata}</ul>
</Container>
<button OnClick={handleSMACompute}> Generate SMA </button>
<Container className="container" maxWidth="sm">
<ul>The value is {sma}</ul>
</Container><div>
</div>
<VictoryChart
theme={VictoryTheme.material}
domainPadding={10}
>
<VictoryBar
style={{ data: { fill: "#c43a31" } }}
data={closingdata}
/>
</VictoryChart>
<div>
<VictoryChart
theme={VictoryTheme.material}
>
<VictoryLine
style={{
data: { stroke: "#c43a31" },
parent: { border: "1px solid #ccc" }
}}
animate={{
duration: 20,
onLoad: { duration: 20 }
}}
containerComponent={<VictoryZoomContainer zoomDomain={{x: [5, 35], y: [0, 100]}}/>}
categories={{
y: dates
}}
data={closingdata}
/>
</VictoryChart>
</div>
</Container>
</>
);
}```
It seems to have been the button setup. I changed to this and it works....??ggrrrr
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{metadata}</li>
</Container>
<button onClick={computeSMA}>
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{sma}</li>
</Container>
In your first code, you used OnClick as the event name. Should be onClick. It is a react syntax and it is case sensitive.

react-beautifull-dnd limit number of items

I am working on React and DND, I am experimenting with an example. For example, I have two columns between which I can drag-and-drop the cards back and forth that works fine, now I want the second column to contain no more than 4 items, but items can be dragged out. In the documentation I read that this was done by setting 'isDropDisabled' to true. Then I can indeed drag no more to this column, and I can leave. Now I've tried influencing that with the this.state.isUnlocked variable. So if there are more than 4 this value will be true otherwise this value will be false. Only the code does not respond to this.state.isUnlocked. Only if I type it directly in but then it is no longer variable. Is there anyone who can help me with this?
This is my code so far:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { Row, Col, Card, CardHeader, CardBody, Button } from "shards-react";
// fake data generator
const getItems = (count, offset = 0) =>
Array.from({ length: count }, (v, k) => k).map(k => ({
id: `item-${k + offset}`,
content: `item ${k + offset}`
}));
// a little function to help us with reordering the result
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
/**
* Moves an item from one list to another list.
*/
const move = (source, destination, droppableSource, droppableDestination) => {
const sourceClone = Array.from(source);
const destClone = Array.from(destination);
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
const result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destClone;
return result;
};
const grid = 8;
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: 'none',
padding: grid * 2,
margin: `0 0 ${grid}px 0`,
// change background colour if dragging
background: isDragging ? 'lightgreen' : 'grey',
// styles we need to apply on draggables
...draggableStyle
});
const getListStyle = isDraggingOver => ({
background: isDraggingOver ? 'lightblue' : 'lightgrey',
padding: grid,
width: 250
});
class qSortOverview extends React.Component {
constructor(props) {
super(props);
this.state = {
items: getItems(10),
selected: getItems(2, 10),
iUnlocked: false,
};
this.canvasRef = React.createRef();
}
/**
* A semi-generic way to handle multiple lists. Matches
* the IDs of the droppable container to the names of the
* source arrays stored in the state.
*/
id2List = {
droppable: 'items',
droppable2: 'selected'
};
getList = id => this.state[this.id2List[id]];
onDragEnd = result => {
const { source, destination } = result;
// dropped outside the list
if (!destination) {
return;
}
if (source.droppableId === destination.droppableId) {
const items = reorder(
this.getList(source.droppableId),
source.index,
destination.index
);
let state = { items };
if (source.droppableId === 'droppable2') {
state = { selected: items };
}
this.setState(state);
} else {
const result = move(
this.getList(source.droppableId),
this.getList(destination.droppableId),
source,
destination
);
this.setState({
items: result.droppable,
selected: result.droppable2
});
//Linker rij
console.log(this.state.items);
if(this.state.items.length > 4){
console.log('to much items');
}
//Rechter rij
console.log(this.state.selected);
if(this.state.selected.length > 4){
this.setState({
isUnlocked: true,
})
}
}
};
// 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() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<Row>
<Col lg="6" md="6" sm="6" className="mb-4">
<Droppable droppableId="droppable" >
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}>
{this.state.items.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}>
{item.content}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</Col>
<Col lg="6 " md="6" sm="6" className="mb-4">
<Droppable droppableId="droppable2" isDropDisabled={this.state.isUnlocked}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}>
{this.state.selected.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}>
{item.content}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</Col>
</Row>
</DragDropContext>
);
}
};
export default qSortOverview;
At the moment the right column contains 2 items, when there are 4 items it should no longer be possible to add items, just remove them.
You need change this rows:
if (this.state.selected.length > 4) {
this.setState({
isUnlocked: true
});
}
to
if (this.state.selected.length > 4) {
this.setState({
isUnlocked: true
});
} else {
this.setState({
isUnlocked: false
});
}

I am trying to use Radium in react

So I have two components, Person and App, the person component has some inline CSS styling that I was to use with react, but I am getting the following error.
Module '"../../../../../../Users/7oss/Desktop/ReactTutorials/my-first-portfolio/node_modules/#types/radium"' has no exported member 'StyleRoot'.ts(2305)
I am trying to add #media styling to the Person Component.
I have tried doing Radium.StyleRoot instead of StyleRoot, but I got the following error:
Objects are not valid as a React child (found: object with keys {#media (min-width: 500px)}). If you meant to render a collection of children, use an array instead.
Here is the App Component:
import React, { Component, ChangeEvent } from "react";
import "./App.css";
import Person from "./Components/Person/Person";
import Radium, { StyleRoot } from "radium";
class App extends Component {
state = {
persons: [
{ id: "hoba", name: "Hosam", age: 24 },
{ id: "hoba1", name: "Ayah", age: 18 },
{ id: "hoba2", name: "Test", age: 20 }
],
ShowPersons: false
};
deletePersonHandler = (personIndex: any) => {
// const persons = this.state.persons.slice(); this is one way
const persons = [...this.state.persons]; // Another way
persons.splice(personIndex, 1);
this.setState({ persons: persons });
};
nameChangeHandler = (event: any, id: any) => {
// console.log('Was clicked!!');
// this.state.persons[0].name = "7ossam" DON'T DO THIS!!! USE SETSTATE()
const personIndex = this.state.persons.findIndex(p => {
return p.id === id;
});
const person = { ...this.state.persons[personIndex] };
person.name = event.target.value;
const persons = [...this.state.persons];
persons[personIndex] = person;
this.setState({
persons: persons
});
};
togglePersonsHanddler = () => {
const doesShow = this.state.ShowPersons;
this.setState({ ShowPersons: !doesShow });
};
render() {
const style = {
backgroundColor: "green",
color: "white",
font: "inherit",
border: "1px solid",
cursor: "pointer",
":hover": {
backgroundColor: "lightgreen",
color: "black"
}
};
let persons = null;
if (this.state.ShowPersons) {
persons = (
<div>
{this.state.persons.map((person, index) => {
return (
<Person
name={person.name}
age={person.age}
click={() => this.deletePersonHandler(index)}
key={person.id}
changedName={(event: any) =>
this.nameChangeHandler(event, person.id)
}
/>
);
})}
</div>
);
style.backgroundColor = "red";
style[":hover"] = {
backgroundColor: "salmon",
color: "black"
};
}
let classes = [];
if (this.state.persons.length <= 2) {
classes.push("red");
}
if (this.state.persons.length <= 1) {
classes.push("bold");
}
return (
<StyleRoot>
<div className="App">
<br />
<p className={classes.join(" ")}>Yasta garab el hoba hoba</p>
<button style={style} onClick={this.togglePersonsHanddler}>
Toggle Names
</button>
<br />
<h1>Hello, this is sam!</h1>
{persons}
</div>
</StyleRoot>
);
}
}
export default Radium(App);
And here is the Person Component:
import React, { Component } from "react";
import Radium from "radium";
import "./Person.css";
interface IPersonProps {
name: string;
age: number;
click?: any;
changedName?: any;
}
class Person extends Component<IPersonProps> {
render() {
const style = {
"#media (min-width: 500px)": {
width: "450px"
}
};
return (
<div className="Person">
{" "}
style={style}
<p onClick={this.props.click}>
{" "}
I am {this.props.name} and I am {this.props.age} years old
</p>
<p>{this.props.children}</p>
<input
type="text"
onChange={this.props.changedName}
value={this.props.name}
/>
</div>
);
}
}
export default Radium(Person);
Here is the radium package in package-lock.json:
"#types/radium": {
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/#types/radium/-/radium-0.24.2.tgz",
"integrity": "sha512-AudCpKQH/csx6eB4OZhEdKf8Avm18wX8gLOig5H5iocPDPp3GRUPkQmUOXvsIYO64LyAb4CiIfSmcWUaUdvl4A==",
"requires": {
"#types/react": "*"
}
},
I am assuming that the issue is with StyleRoot isn't imported correctly somehow, but I would appreciate some input. I am also Using TypeScript
I had the same problem. I managed to solve by making the typing of the constant style explicit.
const style: Radium.StyleRules = {
'#media (min-width: 500px)': {
width: '450px'
}
};

Resources