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

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

Related

What am I doing wrong? when my season dropdown (AsyncSelect) renders my seasons, it shows season 1 twice? not season 1 then season 2?

`What am I doing wrong? when my season dropdown (AsyncSelect) renders my seasons, it shows season 1 twice? not season 1 then season 2?
Also in the same modal I want to run the episode items via Flatlist, but initially season 1 then 2 and so on in asyncSelect, if yal know of a better dropdown or current picker for reactjs.
import { ReactNode, SetStateAction, useEffect, useRef, useState } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { CheckIcon, HandThumbUpIcon, PlusIcon, SpeakerWaveIcon, SpeakerXMarkIcon, XMarkIcon } from "#heroicons/react/24/outline";
import MuiModal from "#mui/material/Modal";
import toast, { LoaderIcon, Toaster } from "react-hot-toast";
import { FaPlay } from "react-icons/fa";
import videojs from "videojs";
import VideoPlayer from "./../src/VideoPlayer";
import FlatList from "flatlist-react";
import Dots from "react-activity/dist/Dots";
import "react-activity/dist/Dots.css";
import Dropdown from 'react-dropdown';
import 'react-dropdown/style.css';
import Select, { ActionMeta, StylesConfig, Theme } from 'react-select';
import AsyncSelect, { useAsync } from 'react-select/async';
import useAuth from "../hooks/useAuth";
import { episodeState, genreState, modalState, seasonsState, vodAssetState } from "../atoms/modalAtom";
//import { Element } from "../typings";
import { VodAsset, Genre, VodSeason, Episode, VideoObject } from "../src/models";
import { View } from "#aws-amplify/ui-react";
import awsvideoconfig from "../src/aws-video-exports";
import { Storage } from "aws-amplify";
import EpisodeItem from "./EpisodeItem";
import { DropdownItem } from "reactstrap";
import VodAssetItem from "./VodAssetItem";
import { DropdownIndicator } from "react-select/dist/declarations/src/components/indicators";
interface ModalProps {
episode: Episode;
episodes: Episode[];
vodAsset: VodAsset;
//video: VideoObject;
seasons: VodSeason[];
season: VodSeason;
genres: Genre[];
genre: Genre;
}
function Modal (this: any, { vodAsset, episode, episodes, seasons, season, genres, genre }: ModalProps): JSX.Element {
const [showModal, setShowModal] = useRecoilState(modalState);
const [items, setItems] = useState([]);
const [selectedItem, setSelectedItem] = useState(null);
const [selectedItems, setSelectedItems] = useState([]);
const [inputValue, setInputValue] = useState('');
const [selectedValue, setSelectedValue] = useState(null);
const [itemValue, setItemValue] = useState('');
const [itemIndex, setItemIndex] = useState('');
const [currentVodAsset, setCurrentVodAsset] = useRecoilState(vodAssetState);
const [currentEpisode, setCurrentEpisode] = useRecoilState(episodeState);
const [currentSeason, setCurrentSeason] = useRecoilState(seasonsState);
const [currentGenre, setCurrentGenre] = useRecoilState(genreState);
//const [vodAssetName, setVodAssetName] = useState(vodAsset?.title);
//const [vodAssetDescription, setVodAssetDescription] = useState(vodAsset?.description);
//const [vodAssetGenre, setVodAssetGenre] = useState(vodAsset?.genreID); //genre?.name);
//const [vodAssetRating, setVodAssetRating] = useState(vodAsset?.popularity);
//const [vodAssetYear, setVodAssetYear] = useState(vodAsset?.release_date);
//const [vodAssetDuration, setVodAssetDuration] = useState(vodAsset?.duration); //duration is a number
//const [vodAssetImage, setVodAssetImage] = useState(vodAsset?.poster);
const [vodAssetVideo, setVodAssetVideo] = useState(vodAsset?.video);
const [vodAssetSeasons, setVodAssetSeasons] = useState(vodAsset?.seasons); //seasons is an array
//const [vodAssetEpisodes, setVodAssetEpisodes] = useState(vodAsset?.episode); //episode is a number
const [vodAssetSeasonsList, setVodAssetSeasonsList] = useState(vodAsset?.seasons); //seasons is an array
//const [vodAssetEpisodesList, setVodAssetEpisodesList] = useState(vodAsset?.episodes); //episode is a number
const seasonNames = seasons ? seasons.map(season => season?.name) : []; //season?.name);
const episodeNames = episodes ? episodes.map(episode => episode?.title) : [];
const [seasonsList, setSeasonsList] = useState(seasonNames);
const [episodesList, setEpisodesList] = useState(episodeNames);
const firstSeason = vodAsset?.seasons?[0]:season!; //season! is a non-null assertion operator, double check this
const firstEpisode = firstSeason?[0]:episode!; //episode! is a non-null assertion operator, double check this
const [selectedSeason, setSelectedSeason] = useState(null); //useState<VodSeason | null>(firstSeason);//(season?.name);
const [selectedEpisode, setSelectedEpisode] = useState(null); //useState<Episode | null>(firstEpisode);//(episode?.title);
const [seasonName, setSeasonName] = useState(seasonNames[0]); //season?.name);
const [selectedSeasonName, setSelectedSeasonName] = useState(seasonNames[0]); //useState<VodSeason | null>(firstSeason);//(season?.name);
const [selectedEpisodeName, setSelectedEpisodeName] = useState(episodeNames[0]); //useState<Episode | null>(firstEpisode);//(episode?.title);
const [muted, setMuted] = useState(false);
const [addedToList, setAddedToList] = useState(false);
const [videoURL, setVideoURL] = useState('');
const [status, setStatus] = useState({});
const [token, setToken] = useState('');
//const [video, setVideo] = useState<VideoObject>();
//const onChangeEpisode = (e: any) => {
//const episodeName = e.value;} //e.value is the selected episode name from the dropdown menu
const toastStyle = {
background: 'black',
color: '#d4af37',
fontWeight: 'bold',
fontSize: '16px',
padding: '15px',
borderRadius: '9999px',
maxWidth: '1000px',
};
//const onChangeSeason = (s: VodSeason) => {
// const seasonNames = seasons ? seasons.map(season => season?.name) : []};
//const seasonName = s.name;} //e.value is the selected season name from the dropdown menu
const [selectedOption, setSelectedOption] = useState(null); //useState<VodSeason | null>(firstSeason);//(season?.name);
const [togglePlaceholderClassName, setTogglePlaceholderClassName] = useState('hidden');
const [toggleMyOptionsClassName, setToggleMyOptionsClassName] = useState('hidden');
const [toggleClassName, setToggleClassName] = useState('hidden');
const [toggleMenuClassName, setToggleMenuClassName] = useState('hidden');
const options = [
{ id: season?.id, value: season?.name, name: season?.name, data: season?.name, label: 'Season 1'},
{ id: season?.id, value: season?.name, name: season?.name, data: season?.name, label: 'Season 2'},
];
{/*const customStyles = {
control: (base: any, state: any) => ({
...base,
background: 'black',
color: 'white',
// match with the menu
borderRadius: state.isFocused ? '3px 3px 0 0' : 3,
// Overwrittes the different states of border
borderColor: state.isFocused ? '#d4af37' : '#d4af37',
// Removes weird border around container
boxShadow: state.isFocused ? null : null,
'&:hover': {
// Overwrittes the different states of border
borderColor: state.isFocused ? '#d4af37' : '#d4af37',
},
}),
menu: (base: any) => ({
...base,
// override border radius to match the box
borderRadius: 0,
// kill the gap
marginTop: 0,
background: 'black',
color: 'white',
}),
menuList: (base: any) => ({
...base,
// kill the white space on first and last option
padding: 0,
}),
option: (base: any, state: any) => ({
...base,
background: 'black',
color: 'white',
// padding: '20px',
// color: state.isSelected ? 'red' : 'blue',
// backgroundColor: state.isSelected ? 'yellow' : 'green',
// the rest of your styles
}),
};*/}
const colorStyles = {
control: (styles: any) => ({ ...styles, backgroundColor: 'black' }),
option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => {
console.log('option', data, isDisabled, isFocused, isSelected);
return {
...styles,
//color: data.color,
backgroundColor: isDisabled
? null
: isSelected
? '#d4af37'
: isFocused
? '#d4af37'
: null,
color: isDisabled
? '#ccc'
: isSelected
? 'black'
: isFocused
? 'black'
: 'white',
cursor: isDisabled ? 'not-allowed' : 'default',
};
}
};
// handle selection
const handleChange = (selectedOption: any) => {
console.log('handleChange', selectedOption);
setSelectedValue(selectedOption);
};
{/*const handleChange = value => {
console.log('handleChange', value);
setSelectedValue(value);
};*/}
// handle input change
//const handleInputChange = (value: SetStateAction<string>) => {
//console.log('handleInputChange', value);
//setInputValue(value);
//};
const loadOptions = (searchValue: string, callback: (arg0: { id: string, data: string, name: string, value: string; label: string; }[]) => void) => {
setTimeout(() => {
const filteredOptions = options.filter(option =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
);
console.log('loadOptions', searchValue, filteredOptions);
callback(filteredOptions);
}, 1000);
};
{/*const { data, error, isLoading } = useAsync({ promiseFn: loadOptions });*/}
const handleClose = () => {
setShowModal(false)
}
const defaultOption = options[0];
const VIDEOJS_OPTIONS = {
autoplay: "play",
controls: true,
responsive: true,
fluid: true,
preload: "auto",
width: 1080,
//muted: {muted},
onplaying,
sources: {
src: awsvideoconfig.awsOutputIVS,
},
useNativeControls: true,
html5: {
nativeAudioTracks: false,
nativeVideoTracks: false,
}
};
const videoOnDemandJsOptions = {
//ref: {vodAsset},
autoplay: "play",
controls: true,
responsive: true,
fluid: true,
preload: "auto",
width: 1080,
token: {token},
displayingVodAsset: true,
//VideoPlaybackQuality: "HIGH",
//muted: {muted},
onplaying,
sources: {
//ref: {vodAsset},
src: { uri: videoURL}, //`https://${awsvideoconfig.awsOutputVideo}/public/RPS-Trailer-2/RPS-Trailer-2.m3u8`, // Test Link: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" / or / uri: videoURL / {uri: episode?.video?.uri},
posterSource: {uri: episode?.poster},
data: {episodes},
//renderEpisodeItem: {EpisodeItem},
episode: currentEpisode,
chosenItem: episode,
posterStyle: {
resizeMode: 'cover',
},
type: "application/x-mpegURL" //"video/mp4"
},
usePoster: true,
useNativeControls: true,
resizeMode: "contain",
//onPlaybackStatusUpdate: {status: setStatus(() => status)},
//episode: currentEpisode,
//data: {episodes},
//renderEpisodeItem: {EpisodeItem}, // this is the component that renders the episode item
//chosenItem: item,
//token: item.video.token,
html5: {
nativeAudioTracks: false,
nativeVideoTracks: false,
}
};
useEffect(() => {
async function fetchVodAsset() {
const result = (await DataStore.query(VodAsset, {id: vodAsset.id})) // double check this
//setVodAsset(result);
console.log("VOD ASSET:", result)
}
fetchVodAsset();
}, [])
//console.log("VOD ASSET:", vodAsset);
useEffect(() => {
if (!vodAsset) {
return;
}
async function fetchSeasons() {
const vodAssetSeasons = (await DataStore.query(VodSeason)).filter(s => s.VodAsset?.id === vodAsset.id)
//setSeasons(seasons);
console.log("SEASONS:", vodAssetSeasons);
setCurrentSeason(vodAssetSeasons[0]);
}
fetchSeasons();
}, [vodAsset])
useEffect(() => {
if (!currentSeason) {
return;
}
async function fetchEpisodes() {
const seasonEpisode = (await DataStore.query(Episode)).filter(e => e.vodSeason?.id === currentSeason.id);
//setEpisodes(seasonEpisode);
console.log("EPISODES:", seasonEpisode);
setCurrentEpisode(seasonEpisode[0])
}
fetchEpisodes();
}, [currentSeason])
if (!vodAsset) {
return <Dots/>
}
console.log("season:", season);
console.log("seasonNames:", seasonNames);
{/*useEffect(() => {
if (episode?.video.startsWith('http')) {
setVideoURL(episode?.video);
return;
}
Storage.get(episode?.video, {level: 'public', contentType: 'application/x-mpegURL', region: 'us-east-1'}).then(setVideoURL);
}, [episode]);*/}
{/*useEffect(() => {
if (!video) {
return;
}
(async () => {
await video?.current?.unloadAsync();
await video?.current?.loadAsync(
{ uri: videoURL },
{},
false
);
})();
}, [videoURL]);*/}
//console.log(videoURL);
//if (videoURL === '') {
//return <View />;
//}
//OR
//useEffect(() => {
//if (!vodAsset) return
//async function fetchVodAsset() {
//const data = await fetch(
//`https://api.themoviedb.org/3/${
//{/*vodAsset?.media_type === 'tv' ? 'tv' : 'movie'*/}
//}/${vodAsset?.id}?api_key=${
//process.env.NEXT_PUBLIC_API_KEY
//}&language=en-US&append_to_response=videos`
//).then((response) => response.json())
//if (data?.videos) {
//const index = data.videos.results.findIndex(
//(element: Element) => element.type === 'Trailer'
//)
//setTrailer(data.videos?.results[index]?.key)
//}
//if (data?.genres) {
//setGenres(data.genres)
//}
//}
//fetchVodAsset()
//}, [vodAsset])
// Find all the movies in the user's list
{/*useEffect(() => {
if (user) {
return onSnapshot(
collection(db, 'customers', user.uid, 'myList'),
(snapshot) => setVodAssets(snapshot.docs)
)
}
}, [db, vodAsset?.id])*/}
// Check if the movie is already in the user's list
{/*useEffect(
() =>
setAddedToList(
vodAssets.findIndex((result) => result.data().id === vodAsset?.id) !== -1
),
[vodAssets]
)*/}
const handleList = async () => {/*
if (addedToList) {
await deleteDoc(
doc(db, 'customers', user!.uid, 'myList', vodAsset?.id.toString()!)
)
toast(
`${vodAsset?.title || vodAsset?.original_name} has been removed from My List`,
{
duration: 8000,
style: toastStyle,
}
)
} else {
await setDoc(
doc(db, 'customers', user!.uid, 'myList', vodAsset?.id.toString()!),
{ ...vodAsset }
)
toast(
`${vodAsset?.title || vodAsset?.original_name} has been added to My List`,
{
duration: 8000,
style: toastStyle,
}
)
}
*/}
return (
<MuiModal
id="modal"
open={showModal}
onClose={handleClose}
className="fixed !top-4 left-0 right-0 z-50 mx-auto w-full max-w-5xl overflow-hidden
overflow-y-scroll rounded-md scrollbar-hide"
>
<>
<Toaster position="bottom-center" />
<button
onClick={handleClose}
className="modalButton absolute right-5 top-5 !z-40 h-9 w-9 border-none
bg-[#181818] hover:bg-[#181818]"
>
<XMarkIcon className="h-6 w-6"/>
</button>
<div className="relative pt-[0%]" key={vodAsset?.id}>
<VideoPlayer episode={currentEpisode} {...videoOnDemandJsOptions} />
<FlatList
list={[]}
data={episodes}
renderItem={function (item: Episode, {/*key: string*/}): JSX.Element | ReactNode {
<EpisodeItem
key={episode.id}
episode={item}
/>;
throw new Error("Function not implemented.");
}}>
</FlatList>
{/*<EpisodeItem episode={episode} />*/}
<div className="absolute bottom-10 flex w-full items-center justify-between px-10">
<div className="flex space-x-2">
<button className="flex items-center gap-x-2 rounded bg-[#d4af37] px-8 text-xl
font-bold text-black transition hover:bg-[#e6e6e6]"
>
<FaPlay className="h-7 w-7 text-black" />
Play
</button>
<button className="modalButton" onClick={handleList}>
{addedToList ? (
<CheckIcon className="h-7 w-7" />
): (
<PlusIcon className="h-7 w-7" />
)}
</button>
<button className="modalButton">
<HandThumbUpIcon className="h-7 w-7" />
</button>
</div>
<button className="modalButton" onClick={() => setMuted(!muted)}>
{muted ? (
<SpeakerXMarkIcon className="h-6 w-6" />
) : (
<SpeakerWaveIcon className="h-6 w-6" />
)}
</button>
</div>
</div>
<div className="flex space-x-16 rounded-b-md bg-[#181818] px-10 py-8">
<div className="space-y-6 text-lg">
<div className="flex items-center space-x-2 text-sm">
<p className="font-semibold text-green-400">
{/*vodAsset!.vote_average * 10*/}98% Match
</p>
<p className="font-light">
{vodAsset?.release_date || vodAsset?.first_air_date}
</p>
<p className="font-light">
{vodAsset?.numberOfSeasons} Seasons
</p>
<div className="flex h-4 items-center justify-center rounded border
border-white/40 px-1.5 bg-[#d4af37] text-black text-xs">
12+
</div>
<div className="flex h-4 items-center justify-center rounded border
border-white/40 px-1.5 text-xs">
HD
</div>
</div>
{/*<View style={{ backgroundColor: 'white' }}>
</View>*/}
{/*currentSeason && (
<Picker
selectedValue={currentSeason}
onValueChange={(itemValue, itemIndex) => {
setCurrentSeason(seasons[itemIndex])
}}
style={{color: 'white', width: 150 }}
itemStyle={{backgroundColor: 'black', maxHeight: 50, marginBottom: -150, marginTop: 20 }}
dropdownIconColor={'white'}
>
{seasonNames?.map(seasonName => (
<Picker color='#d4af37' label={seasonName} value={seasonName} key={seasonName} />
))}
</Picker>
)*/}
{currentSeason &&
<>
{seasonNames?.map(seasonName => (
<option id={seasonName} label={seasonName} value={seasonName} key={season.id}>{seasonName}</option>
))}
<AsyncSelect value={seasons?.map(season => ({value: season.name, label: season.name}))} onChange={handleChange} //value={seasons?.map(season => ({ id: season?.id, name: season.name, value: season.name, label: season.name}))} onChange={handleChange} //value={seasonName}
//value={seasons?.map(season => ({value: season.name, label: season.name}))}
className="select"
classNamePrefix="select"
autoFocus={true}
defaultValue={options[0]}
//defaultValue={seasons?.map(season => ({value: season.name, label: season.name}))}
isSearchable={false}
name="season"
cacheOptions={false}
loadOptions={loadOptions}
defaultOptions={true}
options={options}
placeholder="Select a season"
styles={colorStyles}
//onChange={onChangeSeason}
//onChange={(itemValue, itemIndex) => {
//setCurrentSeason(seasons[itemIndex])
//}}
//controlShouldRenderValue={true}
//isClearable={true}
isMulti={true}
//isOptionDisabled={true}
//isOptionSelected={true}
//isSearchable={true}
//isDisabled={true}
//isFocused={true}
//isLoading={true}
//isRtl={true}
//isInvalid={true}
//menuIsOpen={true}
//isMenuFocused={true}
//isMenuSelected={true}
/>
</>}
<div className="flex flex-col gap-x-10 gap-y-4 font-light md:flex-row">
<p className="w-5/6">{/*vodAsset?.overview*/}{vodAsset?.description}
</p>
<div className="flex flex-col space-y-3 text-sm">
<div>
<span className="text-[gray]">Cast: </span>
{vodAsset?.cast}
</div>
<div>
<span className="text-[gray]">Creator: </span>
{vodAsset?.creator}
</div>
<div>
<span className="text-[gray]">Genres: </span>
{/*genres?.map((genre: { title: any; }) => genre?.title)*/}
{/*vodAsset?.genreID*/}
</div>
<div>
<span className="text-[gray]">Original Language: </span>
{vodAsset?.original_language}
</div>
<div>
<span className="text-[gray]">Original Country: </span>
{vodAsset?.origin_country}
</div>
<div>
<span className="text-[gray]">Total Votes: </span>
{vodAsset?.vote_count}</div>
</div>
</div>
</div>
</div>
</>
</MuiModal>
)
}
export default Modal`````

How to create a reusable component for ant design table columns filter that we can use it for all our table?

I want to add filter, search to ant design tables. Based on the documentation, we need to add some props to table column but it will be repeatative. Is there a way that we can creat a seperate component for the columns and filtering those columns?
I tried to develop that component but the column props' do not accept JSX.
I've done it in this way:
SearchHighliter:
const HighlighterWrapper = memo(({ searchWords, textToHighlight }) => (
<Highlighter
highlightStyle={{
backgroundColor: '#ffc069',
padding: 0,
}}
searchWords={searchWords}
autoEscape
textToHighlight={textToHighlight}
/>
))
FilterHook:
import { useRef, useState } from 'react'
import { Button, Input, Space } from 'antd'
import { SearchOutlined } from '#ant-design/icons'
const useTableFilter = () => {
const [searchText, setSearchText] = useState('')
const [searchedColumn, setSearchedColumn] = useState('')
const searchInput = useRef(null)
const handleSearch = (selectedKeys, confirm, dataIndex) => {
confirm()
setSearchText(selectedKeys[0])
setSearchedColumn(dataIndex)
}
const handleReset = (clearFilters, confirm) => {
clearFilters()
setSearchText('')
confirm()
}
const getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div
style={{
padding: 8,
}}
>
<Input
ref={searchInput}
placeholder="Search text"
value={selectedKeys[0]}
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
style={{
marginBottom: 8,
display: 'block',
}}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{
width: 90,
}}
>
Search
</Button>
<Button
onClick={() => clearFilters && handleReset(clearFilters, confirm)}
size="small"
style={{
width: 90,
}}
>
Reset
</Button>
</Space>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined
style={{
color: filtered ? '#1890ff' : undefined,
marginRight: 10,
}}
/>
),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInput.current?.select(), 100)
}
},
})
return [searchText, searchedColumn, getColumnSearchProps]
}
export default useTableFilter
Table:
const SummaryReport = () => {
const [data, setData] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [searchText, searchedColumn, getColumnSearchProps] = useTableFilter()
const columns = [
{
title: () => <span style={{ paddingLeft: 8 }}>Name</span>,
key: 'name',
dataIndex: 'name',
className: 'no-padding-cell-report',
width: 250,
...getColumnSearchProps('name'),
onFilter: (value, record) => get(record, 'name').toString().toLowerCase().includes(value.toLowerCase()),
render: (_, record) => (
<div style={{ padding: 8 }}>
{searchedColumn === 'name' ? (
<HighlighterWrapper
searchWords={[searchText]}
textToHighlight={get(record, 'name') ? get(record, 'name').toString() : ''}
/>
) : (
get(record, 'name')
)}
</div>
),
},
]
....
}
All you need is just provide 2 functions to your column:
onFilter - here you describe your filter logic, if your data object is simple like in my case, the function also is trivial
render - you need to provide this function if you want to highlight text that the user inputs to filter input.

setState not updating value with object

This is my initial state:
const [inputList, setInputList] = useState([
{ answer: "Answer 1", isCorrect: false, points: 0 },
]);
I am adding input field dynamically and initially there will be one field. There is an Add button through which I can add more fields and remove button with that I can remove fields. But the problem I'm facing, after submitting the value I want to clear all the fields and there should be only one field as it was initially.Suppose if I add 4 fields and after submitting there should be only one. But when I'm trying to do that its not working but add, remove and even setting empty the state is working but setting only one field is not working.I'm mapping depending on inputList value but still after submitting I'm getting all the fields. If I have added 4 fields then after submitting also its 4. The state value is not being changed at all.
ADD more function:
const handleAddClick = () => {
let count = inputList.length;
let newfield = {
answer: `Answer ${count + 1}`,
isCorrect: false,
points: 0,
};
setInputList([...inputList, newfield]);
};
remove function:
const handleRemoveClick = (index) => {
let list = [...inputList];
let newList = list.filter((item, ind) => ind !== index);
setInputList(newList);
};
submit function: This should reset the state to initial state that is only one field should be there But not working
const Save = () => {
setInputList([{ answer: "Answer 1", isCorrect: false, points: 0 }])
}
I tried to do loop also for deleting every index but it only removes one no matter how many times the loop runs
Whole Component:
import DeleteIcon from "#mui/icons-material/Delete";
import React from "react";
import Checkbox from "#mui/material/Checkbox";
import Radio from "#mui/material/Radio";
import FormControl from "#mui/material/FormControl";
import IconButton from "#mui/material/IconButton";
import Switch from "#mui/material/Switch";
import TextField from "#mui/material/TextField";
import { useState } from "react";
import PlusSign from "../../../../Files/img/plus.png";
import Header from "../../../Header/Header";
import Topbar from "../../../Topbar/Topbar";
import styles from "../MultipleResponse/MultipleResponse.module.scss";
import QuestionEditor from "../../Editor/QuestionEditor/QuestionEditor";
import ReactTag from "../../TagInput/ReactTag";
import FormControlLabel from "#mui/material/FormControlLabel";
import { styled } from "#mui/material/styles";
import axios from "axios";
import "react-quill/dist/quill.snow.css";
import AnswerEditor from "../../Editor/AnswerEditor/AnswerEditor";
import SelectComp from "../../SelectComp/SelectComp";
import {
timeOptions,
categoryOptions,
difficultyLevelOptions,
} from "../../../../utils/constants";
import { RadioGroup } from "#mui/material";
import Snackbars from "../../../Assesment/MyAssesment/Snackbar";
import { useEffect } from "react";
export default function MultipleChoice() {
const baseURL = "http://localhost:5000/api/question";
const [distributePoints, setDistributePoints] = useState(false);
const [inputList, setInputList] = useState([
{ answer: "Answer 1", isCorrect: false, points: 0 },
]);
const [question, setQuestion] = useState("");
const [tags, setTag] = useState([]);
const [title, setTitle] = useState("");
const [time, setTime] = useState("");
const [difficultyLevel, setdifficultyLevel] = useState("");
const [category, setCategory] = useState("");
const [whatToLook, setwhatToLook] = useState("");
const [questionRelevent, setQuestionRelevent] = useState("");
const [shuffle, setShuffle] = useState(false);
const [value, setValue] = React.useState("");
const [loading, setLoading] = useState(false);
const [loading2, setLoading2] = useState(false);
const [snackText, setSnackText] = useState("");
const [severity,setSeverity]=useState("")
useEffect(() => {
const items = JSON.parse(localStorage.getItem("tempData"));
if (items) {
setTitle(items[0].title);
setdifficultyLevel(items[0].level);
setwhatToLook(items[0].whatToLookFor);
setQuestionRelevent(items[0].whyQuestionIsRelevant);
setTag(items[0].tags);
setTime(items[0].time);
setInputList(items[0].answers);
setCategory(items[0].category);
setQuestion(items[0].detail);
setShuffle(items[0].shuffleAnswers);
setDistributePoints(items[0].distributePoints);
setValue(items[0].radioValue);
}
}, []);
const [snack, setSnack] = React.useState(false);
const closeSnackbar = (event, reason) => {
if (reason === "clickaway") {
return;
}
setSnack(false);
};
const IOSSwitch = styled((props) => (
<Switch
focusVisibleClassName=".Mui-focusVisible"
disableRipple
{...props}
/>
))(({ theme }) => ({
width: 40,
height: 24,
padding: 0,
marginRight: 10,
"& .MuiSwitch-switchBase": {
padding: 0,
margin: 2,
transitionDuration: "300ms",
"&.Mui-checked": {
transform: "translateX(16px)",
color: "#fff",
"& + .MuiSwitch-track": {
backgroundColor:
theme.palette.mode === "dark" ? "#3699FF" : "#3699FF",
opacity: 1,
border: 0,
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: 0.5,
},
},
"&.Mui-focusVisible .MuiSwitch-thumb": {
color: "#33cf4d",
border: "6px solid #fff",
},
"&.Mui-disabled .MuiSwitch-thumb": {
color:
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[600],
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
},
},
"& .MuiSwitch-thumb": {
boxSizing: "border-box",
width: 20,
height: 20,
backgroundColor: "#FFFFFF",
},
"& .MuiSwitch-track": {
borderRadius: 26 / 2,
backgroundColor: theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
opacity: 1,
transition: theme.transitions.create(["background-color"], {
duration: 500,
}),
},
}));
const switchHandler = (event) => {
setDistributePoints(event.target.checked);
};
const handleInputChange = (e, index) => {
const list = [...inputList];
list[index]["answer"] = e;
setInputList(list);
};
const handlePointChange = (e, index) => {
const { name, value } = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
const handleRemoveClick = (index) => {
let list = [...inputList];
let newList = list.filter((item, ind) => ind !== index);
setInputList(newList);
};
const handleAddClick = () => {
let count = inputList.length;
let newfield = {
answer: `Answer ${count + 1}`,
isCorrect: false,
points: 0,
};
setInputList([...inputList, newfield]);
};
const SaveAndAddAnother = () => {
setLoading2(true);
const newInputList = inputList.map(({ points, ...rest }) => {
return rest;
});
try {
axios
.post(baseURL, {
questionType: "MULTIPLE_CHOICE",
time: time.toString(),
title: title,
level: difficultyLevel,
detail: question,
category: category,
tags: tags,
whatToLookFor: whatToLook,
whyQuestionIsRelevant: questionRelevent,
answers: distributePoints ? inputList : newInputList,
distributePoints: distributePoints,
shuffleAnswers: shuffle,
})
.then((response) => {
setLoading(false);
if (response.data.result === 1) {
setSnack(true);
setSeverity('success')
setTimeout(() => {
setLoading2(false);
}, 1000);
setSnackText("Question added successfully");
setTime("");
setTitle("");
setdifficultyLevel("");
setQuestion("");
setCategory("");
setTag([]);
setwhatToLook("");
setQuestionRelevent("");
setDistributePoints(false);
setShuffle(false);
setValue("A");
let newList = [{ answer: "Answer 1", isCorrect: false, points: 0 }];
setInputList(newList);
localStorage.removeItem("tempData");
} else {
setLoading(false);
}
})
.catch((err) => {
// Handle error
setLoading2(false);
setSnack(true)
setSnackText("Error Adding question")
setSeverity('error')
});
} catch (e) {
setLoading(false);
}
};
const Save = () => {
setInputList([{ answer: "Answer 1", isCorrect: false, points: 0 }])
// setSnack(true);
// setSnackText("Question Saved as draft");
// setSeverity('success')
// setLoading(true);
// const localData = [
// {
// questionType: "MULTIPLE_CHOICE",
// time: time.toString(),
// title: title,
// level: difficultyLevel,
// detail: question,
// category: category,
// tags: tags,
// whatToLookFor: whatToLook,
// whyQuestionIsRelevant: questionRelevent,
// answers: inputList,
// distributePoints: distributePoints,
// shuffleAnswers: shuffle,
// radioValue: value,
// },
// ];
// localStorage.setItem("tempData", JSON.stringify(localData));
// setTimeout(() => {
// setLoading(false);
// }, 2000);
};
const questionText = (value) => {
setQuestion(value);
};
const selectedTags = (tags) => {
setTag(tags);
};
const handleTitle = (e) => {
setTitle(e.target.value);
};
const handleTime = (e) => {
setTime(e.target.value);
};
const handleDifficulty = (e) => {
setdifficultyLevel(e.target.value);
};
const handleCategory = (e) => {
setCategory(e.target.value);
};
const handleWhatToLookFor = (e) => {
setwhatToLook(e.target.value);
};
const handleQuestionRelevent = (e) => {
setQuestionRelevent(e.target.value);
};
const handleShuffle = (event) => {
setShuffle(event.target.checked);
};
function handleRadioButton(event, i) {
if (event.target.value === value) {
setValue("");
} else {
setValue(event.target.value);
}
const list = [...inputList];
Object.keys(list).forEach((key) => {
list[key]["isCorrect"] = false;
});
list[i]["isCorrect"] = true;
setInputList(list);
}
return (
<div className={styles.mainContainer}>
<Header />
<Topbar
questionType="Multiple-response"
loading={loading}
loading2={loading2}
SaveAndAddAnother={SaveAndAddAnother}
save={Save}
/>
<div className={styles.Container}>
<div className={styles.questionContainer}>
<div className={styles.left}>
<p className={styles.question}>Question</p>
<div className={styles.input}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.title}
placeholder="Title.."
value={title}
onChange={(e) => handleTitle(e)}
/>
<QuestionEditor
question={question}
questionText={questionText}
theme="snow"
/>
</div>
<div className={styles.category}>
<ReactTag tags={tags} selectedTags={selectedTags} />
</div>
</div>
<div className={styles.right}>
<div className={styles.rightText}>
<span className={styles.heading}>Select the right answer</span>
<div>
<IOSSwitch
checked={distributePoints}
onChange={switchHandler}
/>
<span className={styles.instructinHeading}>
Distribute points across answers
</span>
</div>
</div>
<div className={styles.answers}>
<div className={styles.radio}>
<FormControl style={{ display: "flex", flex: 1 }}>
<RadioGroup
aria-labelledby="demo-controlled-radio-buttons-group"
name="controlled-radio-buttons-group"
value={value}
>
{inputList.map((x, i) => {
return (
<div key={i}>
<div key={i} className={styles.options}>
{!distributePoints ? (
<FormControlLabel
key={i}
value={x.answer}
control={
<Radio
onClick={(e) => handleRadioButton(e, i)}
/>
}
defaultChecked={false}
/>
) : (
""
)}
<div className="editor">
<AnswerEditor
handleAnswer={handleInputChange}
index={i}
theme="bubble"
val={x.answer}
/>
</div>
{distributePoints ? (
<div className={styles.inputCounter}>
<TextField
variant="standard"
name="points"
InputProps={{
disableUnderline: true,
type: "number",
}}
inputProps={{
min: 0,
style: { textAlign: "center" },
}}
value={x.points}
onChange={(e) => handlePointChange(e, i)}
/>
</div>
) : (
""
)}
{inputList.length > 1 && (
<IconButton
onClick={() => handleRemoveClick(i)}
className={styles.icon}
>
<DeleteIcon
sx={{ fontSize: 24, color: "#3699FF" }}
/>
</IconButton>
)}
</div>
{inputList.length - 1 === i && (
<div className={styles.bottomItemContainer}>
{distributePoints ? (
<div className={styles.pointsInfoText}>
Allocate points across answers. Give the best
answer 5 points
</div>
) : (
""
)}
{!inputList.some((el) => el.isCorrect === true) &&
!distributePoints ? (
<div className={styles.pointsInfoText}>
Select the correct answer
</div>
) : (
""
)}
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<button
onClick={handleAddClick}
type="button"
className={styles.addButton}
>
<img src={PlusSign} alt="" /> Add another
answer
</button>
<div className={styles.label}>
<Checkbox
checked={shuffle}
onChange={handleShuffle}
style={{ color: "#00A3FF" }}
/>
Shuffle answer
</div>
</div>
</div>
)}
</div>
);
})}
</RadioGroup>
</FormControl>
</div>
</div>
</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.timeContainer}>
<SelectComp
handleChange={handleTime}
title="Time to answer the question"
val={time}
optionData={timeOptions}
/>
</div>
<div className={styles.timeContainer}>
<SelectComp
handleChange={handleCategory}
title="Question Category"
val={category}
optionData={categoryOptions}
/>
</div>
</div>
<div className={styles.itemContainer}>
<div style={{ flex: 1 }}>What to look for in the answer?</div>
<div style={{ flex: 1 }}>Why is this question relevant?</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.releventText}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.text}
placeholder="What to look for in the answer..."
value={whatToLook}
onChange={(e) => handleWhatToLookFor(e)}
/>
</div>
<div className={styles.releventText}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.text}
placeholder="Why is this question relevant.."
value={questionRelevent}
onChange={(e) => handleQuestionRelevent(e)}
/>
</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.difficultyLevel}>
<SelectComp
handleChange={handleDifficulty}
title="Difficulty Level"
val={difficultyLevel}
optionData={difficultyLevelOptions}
/>
</div>
{/* <div style={{ flex: 1 }}>Why is this question relevant?</div> */}
</div>
<div className={styles.bottomInfo}>
<div className={styles.element}>
<div className={styles.circle}></div>
<div className={styles.text}>
How should I formate my questions?
</div>
</div>
<div className={styles.element}>
<div className={styles.circle2}></div>
<div className={styles.text}>
{" "}
How do I use the question editor?
</div>
</div>
<div className={styles.element}>
<div className={styles.circle3}></div>
<div className={styles.text}>How do I use the formula editor?</div>
</div>
</div>
</div>
{snack && (
<Snackbars
text={snackText}
snack={snack}
closeSnackbar={closeSnackbar}
severity={severity}
/>
)}
{/* <div style={{ marginTop: 20 }}>{JSON.stringify(inputList)}</div> */}
</div>
);
}

React component not re-rendering when useEffect dependency changes

PROBLEM:
I am currently creating a react app that allows you to checkout a book to a professor. In this app it has a couple of things that need to obviously update when a user checks out a book.
So first off there is the number of totalBooks that is checked out, or just when the entire book object changes then the component should re-render.
I have a useEffect function that is making an api call to a mongodb database and is accessing a document that will yield a book object in the response to the react app. Here is that useEffect function:
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
setBook({...book.data.data})
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
I have even subbed out the book object dependency for something like book.checkedOutCopies. Which should return a number and if that number is different from the last then it should re-render the component. This is however, not the case. No matter what I try I am unable to re-render the component when this document changes. I even created a number called reRender and updated it when the api call to checkout a book finished its call. This would be undesired even if it worked because it would not change for someone who was already on the page, but not on the same computer as the person that clicked the checkout button.
I simply just want this component to re-render when the book object in the mongo db database has changed. Which from my understanding the right way to do it is above. The problem is that even after I successfully checkout a book the state never updates. The number of checked out books on the screen stays static:
Here is a screen shot of what the page looks like:
The green books should turn to red when the update button is clicked and a success status is responded. The Total Checked Out should also change. Neither of these happen.
Here is the book object:
const Book = new Schema(
{
bookName: { type: String, required: true },
bookDesc: { type: String, required: false},
numCheckedOut: { type: Number, required: false },
copiesAvail: {type: Number},
whosChecked: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'prof'
}],
copies: [Copy],
ISBN: { type: Number, required: true },
nextDue: {type: String},
nextProf: {type: Schema.Types.ObjectId, ref: 'prof'}
},
{ timestamps: true },
)
I don't understand why it isn't updating would appreciate any help here is the file in its entirety:
import React, { useState, useEffect, useRef } from 'react'
import api from '../api'
import { InputGroup, Form, FormControl, Container, Button, Col, Row, Toast } from 'react-bootstrap'
import { FaBook } from 'react-icons/fa'
import BookIconFunc from '../components/helperComponents/bookIconFunc'
import Select from 'react-dropdown-select';
import './bookslist.css'
import Axios from "axios";
import BookIcoContext from '../context/BookIconContext';
import DatePicker from "react-datepicker";
import ColoredLine from '../components/helperComponents/ColoredLine'
import CheckoutBookHistroy from '../components/CheckoutBookHistory/CheckoutBookHistory'
import { useHistory } from 'react-router-dom';
const handleCheckout = async (e) => {
e.preventDefault();
}
const topLeftBook = {
marginTop: "1.0rem",
display: "flex",
width:"fit-content",
height: "fit-content",
justifyContent: "left",
flexDirection: "column",
alignItems: "center",
borderRadius: "0px",
border: "2px solid #73AD21",
padding: "0.5rem 2.5rem",
}
const booksRows = {
// marginTop: "4.0rem",
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
padding: "1.0rem",
justifyContent: "left",
// border: "2px solid black",
width: "50%",
marginLeft: "1.0rem"
}
const bottomForm = {
flexGrow: "1"
}
const indBook = {
margin: "0.5rem",
color: "green"
}
const updateButtonStyle = {
display: "flex",
width: "100%",
justifyContent:"center"
}
const topOfPage = {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
verticalAlign: "middle"
}
const bookIcon = {
width: "10.0rem",
}
const bottomOfPage = {
display: "flex",
flexDirection: "row",
marginBottom: "1.0rem",
verticalAlign: "middle",
flexGrow: "1",
marginLeft: "1.0rem"
}
const topForm = {
width: "100%",
marginLeft: "2.0rem",
verticalAlign: "middle",
alignItems: "center",
justifyContent: "center"
}
export default function BooksUpdate(props) {
/* TOP FORM VARIABLES */
const [book, setBook] = useState(null)
const [bookName, setBookName] = useState()
const [desc, setDesc] = useState()
const [ISBN, setISBN] = useState()
const [copies, setCopies] = useState()
const history = useHistory();
/* BOOK ICON CONTEXT STATE */
const [iconState, setIconState] = useState(false)
/* re render state when */
const [reRender, setReRender] = useState(0);
/* BOTTOM FORM VARIABLES */
const [newCopies, setNewCopies] = useState()
const [checkoutCopiesNum, setCheckoutCopiesNum] = useState(0)
const [numCheckedOut, setNumCheckedOut] = useState(0)
const [allProfs, setAllProfs] = useState()
const [profChosen, setProfChosen] = useState()
const [bookicoref, setIcoRefs] = useState([]);
const [dueDate, setDueDate] = useState(new Date());
var anotherRender = 0;
const submitCheckout = async (e) => {
e.preventDefault();
try {
const checkoutBookData = {book, checkoutCopiesNum, profChosen, dueDate};
const checkoutBookRes = await Axios.post("http://localhost:8174/api/book/checkout/" + props.id, checkoutBookData)
if(checkoutBookRes.statusText === 'OK'){
setShowCheckoutToast(!showCheckoutToast)
}
/* Display toast that confirms the book was checked out */
setReRender(reRender+1)
anotherRender = anotherRender + 1
// history.push("/Books/Update/" + props.id)
}
catch (err) {
alert(err)
}
}
const handleSetBook = (bookData) => {
setBook({...bookData})
}
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
await handleSetBook(book.data.data)
// setBook(book.data.data)
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
useEffect( () => {
const getProfs = async () => {
await Axios.get('http://localhost:8174/user/professors').then((ps) => {
var array = []
ps.data.data.map((prof, index) => {
array.push({label: prof.name, value: prof, key: prof._id})
})
setAllProfs(array)
})
}
getProfs()
}, [])
/* EFFECT TO CREATE REFS FOR EACH BOOK ICON */
useEffect(() => {
// add or remove refs
copies &&
setIcoRefs(bookicorefs => (
Array(copies.length).fill().map((_, i) => bookicorefs[i] || React.createRef())
))
}, [copies]);
const handleUpdate = () => {
console.log("handling update")
}
const findCheckedOut = (array) => {
var numChecked = 0;
array.filter(arr => {
if(arr.checkedOut){
numChecked = numChecked + 1
}
})
return numChecked
}
const [showCheckoutToast, setShowCheckoutToast] = useState(false)
const toggleCheckToast = () => {
setShowCheckoutToast(!showCheckoutToast)
}
/* EFFECT TO VALIDATE THE INFORMATION INSIDE THE CHECKOUT BOOKS FIELD */
useEffect(() => {
if(!copies){
return
}
if(checkoutCopiesNum > copies.length){
alert("There isn't that much of this book available")
return;
}
// console.log(numCheckedOut)
if(checkoutCopiesNum > (copies.length - numCheckedOut)){
setCheckoutCopiesNum(0)
alert('You cannot checkout that many copies as there is already to many checked out')
return;
}
// for(var i = 0; i < checkoutCopiesNum; i++){
// }
},[checkoutCopiesNum, numCheckedOut])
return (
book ?
copies ?
<div>
<Container>
{/* Show the book icon with the title of the book underneath */}
<div style={topOfPage}>
<div style={topLeftBook}>
<FaBook style={bookIcon} size={200}/>
<h4 className="">{book.bookName}</h4>
</div>
<Form style={topForm}>
<Row>
<Col className="pl-0">
<InputGroup className="mb-3 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Book Name</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setBookName(e.target.value)}
defaultValue={book.bookName}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Form.Group controlId="exampleForm.ControlTextarea1">
<Form.Control as="textarea" rows={5} defaultValue={book.bookDesc}/>
</Form.Group>
</Col>
<Col className="m-0 pr-0">
<InputGroup className="mb-4 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">ISBN</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setISBN(e.target.value)}
defaultValue={book.ISBN}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<InputGroup className="mb-4 mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Copies</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={copies.length}
/>
</InputGroup>
<InputGroup className="mb-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Checked Out</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={findCheckedOut(book.copies)}
/>
</InputGroup>
</Col>
<Button style={updateButtonStyle} onClick={handleUpdate}>Update</Button>
</Row>
</Form>
</div>
<Row style={{justifyContent: "space-between", verticalAlign: "middle"}}>
<Toast
show={showCheckoutToast}
onClose={toggleCheckToast}
style={{
position: 'absolute',
top: 0,
right: 0,
}}
>
<Toast.Header>
<img
src="holder.js/20x20?text=%20"
className="rounded mr-2"
alt=""
/>
<strong className="mr-auto">Success!</strong>
</Toast.Header>
<Toast.Body>Successfully Checked out a Book</Toast.Body>
</Toast>
<div style={bottomOfPage}>
<Form style={bottomForm} onSubmit={submitCheckout}>
<InputGroup className="mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon4">Checkout Out Copies:</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
onChange={(e) => setCheckoutCopiesNum(e.target.value)}
placeholder={checkoutCopiesNum}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Select
className="mt-4"
style={{width: "100%"}}
name="Select"
required
// loading
searchable
placeholder="To:"
options={allProfs}
onChange={(values) => {setProfChosen(values[0].value)}}
/>
<DatePicker className="mt-4" selected={dueDate} onChange={date => setDueDate(date)} />
<Button type="submit" className="mt-3 w-100">Checkout</Button>
</Form>
</div>
<BookIcoContext.Provider value={{iconState, setIconState}}>
<div style={booksRows} onClick={() => setIconState(true)} onMouseUp={() => setIconState(false)}>
{
copies ? copies.map((copy, index) => {
return <div
key={index}
>
<BookIconFunc
checkedOut={copy.checkedOut}
ref={bookicoref[index]}
>
</BookIconFunc>
</div>
})
:
<div>none</div>
}
</div>
</BookIcoContext.Provider>
</Row>
</Container>
<ColoredLine color="grey" m={20} height={1}/>
<Container>
{/* {book.whosChecked.map(prof => {
// console.log(prof)
// <Col>{prof}</Col>
})} */}
<CheckoutBookHistroy book_id={props.id} book={book} reRender={reRender}></CheckoutBookHistroy>
</Container>
</div>
:
<div>no data</div>
:
<div>no data</div>
)
}

How to implement drag and drop behaviour in React

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

Categories

Resources