DraftJS blockstyle (header-one) not getting triggered - reactjs

I have used draftJS in my project as a note taker. The inline styles are working perfectly fine but the blockType aren't.
Used the RichUtils to toggleInlineStyles and toggleBlockType.
Main DraftJS.jsx file :
class PageContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
alignment: "align-left",
};
this.setDomEditorRef = ref => (this.domEditor = ref);
this.focus = () => this.domEditor.focus();
this.onChange = editorState => {
this.setState({
...this.state,
editorState,
});
};
}
handleKeyCommand = command => {
const newState = RichUtils.handleKeyCommand(
this.state.editorState,
command,
);
if (newState) {
this.onChange(newState);
return "handled";
}
return "not-handled";
};
blockStyleFn = () => {
return this.state.alignment;
};
handleAlignment = alignment => {
this.setState({
...this.state,
alignment,
});
};
toggleInlineStyle = inlineStyle => {
this.onChange(
RichUtils.toggleInlineStyle(this.state.editorState, inlineStyle),
);
};
toggleBlockType = blockType => {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
};
render() {
return (
<div className="flex items-center justify-center h-screen ">
<div className="overflow-hidden shadow-md min-w-[422px] rounded-2xl ">
<div
onClick={this.focus}
className="editors min-h-[210px] break-words text-base p-6 bg-white border-b-[1px] border-primary-400">
<Editor
blockStyleFn={this.blockStyleFn}
className="text-secondary-400"
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
placeholder="Take notes here..."
spellCheck={true}
ref={this.setDomEditorRef}
/>
</div>
<div className="flex justify-between py-4 px-6 ">
<div className="flex flex-1 justify-between">
<InlineStyleControls
editorState={this.state.editorState}
onToggleInLineStyle={this.toggleInlineStyle}
onToggleBlock={this.toggleBlockType}
focus={this.focus}
/>
</div>
<VerticalBorder className="mx-7" />
<AlignTextControls
handleAlignment={this.handleAlignment}
focus={this.focus}
currentAlignment={this.state.alignment}
/>
</div>
</div>
</div>
);
}
}
InLineStyleControls.jsx :
const InlineStyleControls = props => {
const INLINE_STYLES = [
{
label: "Bold",
style: "BOLD",
icon: bold => <BoldIcon bold={bold} />,
},
{
label: "Italic",
style: "ITALIC",
icon: bold => <ItalicIcon bold={bold} />,
},
{
label: "Underline",
style: "UNDERLINE",
icon: bold => <UnderlineIcon bold={bold} />,
},
{
label: "H1",
style: "header-one",
icon: bold => <TextIcon bold={bold} />,
},
];
let currentStyle = props.editorState.getCurrentInlineStyle();
const selection = props.editorState.getSelection();
const blockType = props.editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();
return (
<div className="flex flex-1 justify-between">
{INLINE_STYLES.map(type => (
<StyleButton
key={type.label}
active={
type.label === "H1"
? type.style === blockType
: currentStyle.has(type.style)
}
icon={type.icon}
onToggle={type.label === "H1" ? props.onToggleBlock : props.onToggleInLineStyle}
style={type.style}
focus={props.focus}
/>
))}
<span className="cursor-pointer">
<TextColor />
</span>
</div>
);
};
StyleButton.jsx:
class StyleButton extends React.Component {
constructor() {
super();
this.onToggle = e => {
e.preventDefault();
this.props.onToggle(this.props.style);
};
}
render() {
return (
<span className={` cursor-pointer ${this.props.class} `} onMouseDown={this.onToggle} onMouseUp={this.props.focus} >
{this.props.icon(this.props.active ? 2 : 1)}
</span>
);
}
}
Problem : Everything is working fine except the "header-one" and I not able to figure out why.
I used the rich editor example to build this.
Edit : I found the problem. The problem was due to tailwind css. When I remove tailwind from my project everything is working fine. any idea on to how to make draftJS work with tailwind?

Related

React select from Radix-UI conflicts with default value, how to solve it?

I have this component where I render 3 different selectors depending on the data I pass. The first selector once clicked, gets the data of the second one (without selection), the same for the second, and so on. What I am trying to achieve is that, if I select the first dropdown && the second one has only one item in the list I get that value will be pre-selected without letting the user select it by himself.
This is the parent component where I pass the default value to each selector:
const CreateBatchTemplateDialog: FC<Props> = ({ open, onClose }) => {
const [localInstruments, setLocalInstruments] = useState<LocalInstrument[]>();
const [corridors, setCorridors] = useState<Corridor[]>();
const [corridorId, setCorridorId] = useState<string>();
const [productSchemas, setProductSchemas] = useState<ProductSchema[]>();
const [dowloaded, setDowloaded] = useState<boolean>(false);
const { data: localInstrumentsData } = useGetLocalInstrumentsQuery();
const downloadFile = useDownloadFile();
const [getProductSchemas] = useLazyGetProductSchemasQuery();
const fetchingProductSchemaData = async () => {
try {
const { data: productSchema } = await getProductSchemas(corridorId);
setProductSchemas(productSchema.data);
} catch (error: any) {
console.error(error);
}
};
useEffect(() => {
if (corridorId) {
fetchingProductSchemaData();
}
}, [corridorId]);
useEffect(() => {
if (localInstrumentsData?.data) {
setLocalInstruments(localInstrumentsData.data);
}
setProductSchemas([]);
}, [localInstrumentsData]);
const {
handleSubmit,
setError,
formState,
setValue,
reset: resetForm,
getValues
} = useForm<Inputs>({
mode: 'onSubmit',
resolver: yupResolver(schema)
});
const localInstrumentsOptions = useMemo(() => {
setProductSchemas([]);
return Array.isArray(localInstruments)
? localInstruments.map((i) => ({ label: i.name, value: i.name }))
: [];
}, [localInstruments]);
const corridorsOptions = useMemo<SelectChoice[]>(() => {
setProductSchemas([]);
return Array.isArray(corridors)
? corridors.map((i) => ({
label: i.label,
value: i.id.toString()
}))
: [];
}, [corridors, localInstruments]);
const productSchemasOptions = useMemo(() => {
return Array.isArray(productSchemas)
? productSchemas.map((i) => ({
label: i.label,
value: i.id.toString()
}))
: [];
}, [productSchemas, corridors, localInstruments]);
const onLocalInstrumentChange = (value: string) => {
setDowloaded(false);
setProductSchemas([]);
setCorridorId(undefined);
const corridorData = localInstruments?.find((i) => i.name === value);
setCorridors(corridorData ? corridorData.products : []);
setValue('localInstrument', value, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setValue('corridor', '', {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
};
const onCorridorChange = (value: string) => {
setDowloaded(false);
setCorridorId(value);
setProductSchemas([]);
setValue('corridor', value, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
setValue('productSchema', '', {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
};
const onProductSchemaChange = (value: string) => {
setDowloaded(false);
setValue('productSchema', value, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true
});
};
const onSubmit: FormEventHandler<HTMLFormElement> = async (event: any) => {
try {
await handleSubmit(async (data) => {
downloadFile(`payouts/schemas/${data.productSchema}/csv_template`);
setDowloaded(true);
})(event);
} catch (error: any) {
setTimeout(
() =>
setFormErrors({
error,
setError,
fields: Object.keys(schema.fields)
}),
50
);
}
};
const onDialogClose = (value: boolean) => {
resetForm();
onClose(value);
setLocalInstruments([]);
setCorridors([]);
setCorridorId(undefined);
setProductSchemas([]);
setDowloaded(false);
};
return (
<Dialog open={open} onOpenChange={onDialogClose}>
<DialogPortal>
<DialogOverlay className="fixed inset-0 z-50 bg-inpay-green-700/90">
<DialogContent
onClick={(e) => e.stopPropagation()}
className="fixed top-[50%] left-[50%] translate-y-[-50%] translate-x-[-50%] rounded-lg shadow-[0_0_6px_#B7BCBA]"
>
<DialogTitle className="flex h-12 items-center justify-between rounded-t-lg border-b border-b-inpay-pumice bg-white px-6">
<DownloadIcon className="w-8 stroke-1 group-hover:stroke-1.5" />
<span className="pr-12">Create batch template</span>
<DialogClose
aria-label="Close"
className="outline-none focus:outline-none"
>
<XIcon className="-m-1 h-8 w-8 outline-none focus:outline-none" />
</DialogClose>
</DialogTitle>
<div className="flex justify-center rounded-b-lg bg-inpay-black-haze-100 p-6">
<div className="w-72">
<form onSubmit={onSubmit} autoComplete="off">
<div className="mb-4 flex flex-col">
<label className="mb-0.5 pl-2 text-xs">Format</label>
<CsvSelector />
</div>
<div className="mb-4 flex flex-col">
<label className="mb-0.5 pl-2 text-xs">Transfer type</label>
<Selector
selectChoices={localInstrumentsOptions}
onChange={onLocalInstrumentChange}
name="localInstruments"
disabled={
!localInstrumentsOptions ||
localInstrumentsOptions.length === 0
}
defaultValue={
localInstrumentsOptions?.length === 1
? localInstrumentsOptions[0].value
: undefined
}
/>
</div>
<div className="mb-4 flex flex-col">
<label className="mb-0.5 pl-2 text-xs">Corridor</label>
<Selector
selectChoices={corridorsOptions}
onChange={onCorridorChange}
name="corridor"
disabled={
!corridorsOptions || corridorsOptions.length === 0
}
value={getValues('corridor')}
defaultValue={
corridorsOptions?.length === 1
? corridorsOptions[0].label
: undefined
}
/>
{console.log(
corridorsOptions?.length === 1
? corridorsOptions[0].label
: undefined
)}
</div>
<div className="mb-10 flex flex-col">
<label className="mb-0.5 pl-2 text-xs">Data required</label>
<Selector
selectChoices={productSchemasOptions}
onChange={onProductSchemaChange}
name="productSchema"
disabled={
!productSchemasOptions ||
productSchemasOptions.length === 0
}
value={getValues('productSchema')}
defaultValue={
productSchemasOptions?.length === 1
? productSchemasOptions[0].label
: undefined
}
/>
</div>
{dowloaded === false ? (
<button
type="submit"
className="inpay-button w-full"
disabled={!formState.isValid}
>
Download CSV template
</button>
) : (
<button
type="submit"
className="inpay-button w-full"
onClick={() => onClose(false)}
>
CSV dowloaded, close window
</button>
)}
{formState.errors.base && (
<div
role="alert"
className="mt-2 rounded-2xl bg-rose-200 p-3 text-sm text-inpay-red-600"
style={{
filter: 'drop-shadow(0 0 1px rgb(0 0 0 / 0.16))'
}}
>
{formState.errors.base.message}
</div>
)}
</form>
</div>
</div>
</DialogContent>
</DialogOverlay>
</DialogPortal>
</Dialog>
);
};
This is the child component where I render the Selector, the issue is that even though the default value (string) and the matchingValue (object) are logged in the useEffect, matched and passed to the setCurrentItem state, the selector won't know when to use the defaultValue. So the preselection never works for some reasons. I am a bit confused if it is a limit of the library or if it is some mistake a made. Here below the selector:
import { FC, useEffect, useState } from 'react';
import {
Select,
SelectContent,
SelectItem,
SelectItemText,
SelectPortal,
SelectScrollDownButton,
SelectScrollUpButton,
SelectTrigger,
SelectValue,
SelectViewport
} from '#radix-ui/react-select';
import { ChevronUpIcon, ChevronDownIcon } from '#heroicons/react/outline';
import cn from 'classnames';
import { twMerge } from 'tailwind-merge';
interface SelectorClassNames {
loadingClassName?: string;
formButtonClassName?: string;
selectedItemClassName?: string;
selectContentClassName?: string;
selectItemListClassName?: string;
}
export interface SelectChoice {
value: string;
label: string;
}
interface Props {
defaultValue?: string;
onChange?: (value: string) => void;
classNames?: SelectorClassNames;
selectChoices: SelectChoice[];
name?: string;
disabled?: boolean;
value?: string;
}
const Selector: FC<Props> = ({
defaultValue,
onChange,
classNames,
selectChoices,
name,
disabled,
value
}) => {
const [currentItem, setCurrentItem] = useState<SelectChoice | undefined>(
undefined
);
const [availableList, setAvailableList] = useState<SelectChoice[]>([]);
const [opened, onOpenChange] = useState<boolean>(false);
const selectTriggerProps = {
className: twMerge(
cn(
'group flex items-center h-12 select-none rounded-lg bg-white px-4 text-left shadow-plain',
classNames?.formButtonClassName,
{
'group hover:shadow-plain-lg cursor-pointer': !disabled,
'stroke-inpay-black-haze-700 stroke-1.5 group hover:none text-inpay-black-haze-700':
disabled,
'bg-inpay-gray-100': disabled && currentItem?.value
}
)
)
};
const selectedItemProps = {
className: twMerge(
cn(
'group flex h-12 cursor-pointer items-center px-4 rounded-t-lg outline-offset-[-1px] hover:bg-inpay-green-200 hover:text-inpay-green-700 border-none focus:outline-none',
classNames?.selectedItemClassName,
{
'px-4': disabled
}
)
)
};
const selectItemListProps = {
className: twMerge(
cn(
'group h-12 flex items-center cursor-pointer px-4 outline-offset-[-1px] first:rounded-t-lg last:rounded-b-lg hover:bg-inpay-green-200 hover:text-inpay-green-700 border-none focus:outline-none',
classNames?.selectItemListClassName
)
)
};
const selectContentProps = {
className: twMerge(
cn(
`${
currentItem
? ' relative z-50 select-none overflow-hidden rounded-lg bg-white shadow-plain-lg w-72'
: 'relative z-50 select-none overflow-hidden rounded-lg bg-white shadow-plain-lg w-72'
}`,
classNames?.selectContentClassName
)
)
};
const onValueChange = (value: string) => {
if (selectChoices) {
const matchingValue = selectChoices.find(
(element) => element.value == value
);
if (matchingValue) {
setCurrentItem({ ...matchingValue });
setAvailableList(
selectChoices.filter((i) => i.value != matchingValue.value)
);
onChange && onChange(value);
}
}
};
useEffect(() => {
if (defaultValue) {
console.log(selectChoices, 'selectChoices'); // gets logged correctly
const newValue = selectChoices.find((i) => i.label === defaultValue);
console.log(defaultValue, 'defaultValue'); // gets logged correctly
if (!newValue) {
console.error('can not find new value');
}
setCurrentItem(newValue);
console.log(newValue, 'new value'); // gets logged correctly
setAvailableList(selectChoices.filter((i) => i.value != defaultValue));
}
}, [defaultValue, selectChoices]);
useEffect(() => {
setAvailableList(selectChoices);
setCurrentItem(undefined);
}, [selectChoices]);
return (
<Select
onValueChange={onValueChange}
onOpenChange={onOpenChange}
name={name}
disabled={disabled}
defaultValue={defaultValue}
value={value}
>
<SelectTrigger {...selectTriggerProps}>
<SelectValue
placeholder={
<div className="flex">
<div className="flex-1"></div>
<ChevronDownIcon
className={cn('w-1/12 stroke-inpay-black-haze-700 stroke-1.5', {
'group-hover:stroke-inpay-black-500': !disabled
})}
/>
</div>
}
/>
</SelectTrigger>
<SelectPortal>
<SelectContent {...selectContentProps}>
<SelectScrollUpButton className="flex justify-center">
<ChevronUpIcon className="h-10 w-6 stroke-1.5" />
</SelectScrollUpButton>
<SelectViewport>
{currentItem && (
<SelectItem
key={currentItem.value}
value={currentItem.value}
{...selectedItemProps}
>
<SelectItemText>
<div className="flex">
<div className="flex-1">
<div>{currentItem.label}</div>
</div>
{opened ? (
<ChevronUpIcon className="w-1/12 stroke-inpay-black-500 stroke-1.5" />
) : (
<ChevronDownIcon className="w-1/12 stroke-inpay-black-haze-700 stroke-1.5 group-hover:stroke-inpay-black-500" />
)}
</div>
</SelectItemText>
</SelectItem>
)}
{availableList.map((item, index) => (
<SelectItem
key={item?.value}
value={item.value}
{...selectItemListProps}
>
<SelectItemText>
<div className="flex">
<div className="flex-1">{item.label}</div>
{index == 0 && !currentItem && (
<ChevronUpIcon className="w-1/12 stroke-inpay-black-500 stroke-1.5" />
)}
</div>
</SelectItemText>
</SelectItem>
))}
</SelectViewport>
<SelectScrollDownButton className="flex justify-center">
<ChevronDownIcon className="h-10 w-6 stroke-1.5" />
</SelectScrollDownButton>
</SelectContent>
</SelectPortal>
</Select>
);
};
export default Selector;
Any clues ?

React vertical Nested List open and close sub List

i have a react.js component in which i am displaying states and under i am displaying districts. To display this list it's working fine. The problem i want when i press any sates only that particular state sublist should display not all states sublist.
import React,{useState} from "react"
Const [open,,setOpen]=useState(false);
//Wrapper component
</div>
{states.map((city, index) => {
return <StateList state={state} key={index} />;
})}
</div>
//state component
<div onClick={()=>setOpen(!open)}>
<span >{state.name}</span>
<svg
viewBox="0 0 24 24"
className={`
${open ? "rotate-180" : ""}
`}
>
</svg>
</h2>
{open && <AreaList area={city} />}
</div>
//district component
const AreaList = ({ state }) => {
return state.districts.map((district) => (
<li>
<span className="ml-2 text-outer-space">
{district.name}
</span>
</li>
));
};
Here is working solution (without styles):
Codesandbox
import { useState } from "react";
const data = [
{
name: "Fujairah",
districts: [
{ name: "Al Buthna" },
{ name: "Al Bedia" },
{ name: "Town Center" },
{ name: "Wadi Al Sedr" }
]
},
{
name: "Abu Dhabi",
districts: [{ name: "Al Aman" }, { name: "Al Bahya" }]
}
];
const App = () => {
return (
<div>
{data.map((city) => {
return <City key={city.name} city={city} />;
})}
</div>
);
};
const City = ({ city }) => {
const [open, setOpen] = useState(false);
return (
<div onClick={() => setOpen(!open)}>
<h2>
<span>{city.name}</span>
</h2>
{open && <DistrictList city={city} />}
</div>
);
};
const DistrictList = ({ city }) => {
return city.districts.map((district) => (
<li key={district.name}>
<span>{district.name}</span>
</li>
));
};
export default App;

Refetch query and cache evict on route change

I have a problem regarding my GraphQL query cache. I'm trying to create pagination for this query. I have some music playlists and every playlist have its own page. On a playlist page I call a query to get the playlist songs (which I get as a JSON response). This is the merge function from my InMemoryCache:
getMoreSpotifyPlaylists: {
keyArgs: [],
merge(existing: PaginatedJsonResponse | undefined, incoming: PaginatedJsonResponse): PaginatedJsonResponse {
var existingJSON, incomingJSON
if (existing?.['jsonData']) {
existingJSON = JSON.parse(existing?.['jsonData'])
} else {
return incoming
}
incomingJSON = JSON.parse(incoming?.['jsonData'])
var finalJSON = {
...incomingJSON,
items: existingJSON?.items.concat(incomingJSON?.items)
}
return {
...incoming,
jsonData: JSON.stringify(finalJSON)
}
}
}
The problem is that when I change the playlist page, the first songs remain the songs from the other playlist. I've tried to evict the cache on page change but this didn't worked.
This is the playlist page:
const SpotifyPlaylistPage: NextPage = () => {
const apolloClient = useApolloClient();
const router = useRouter()
const [rerender, setRerender] = useState(false)
const [progress, setProgress] = useState(100)
var id = typeof router.query.id === "string" ? router.query.id : ""
const { t: menuTranslation } = useTranslation('menu')
const { t: commonTranslation } = useTranslation('common')
const { t: actionsTranslation } = useTranslation('actions')
const { data: playlistData, loading: playlistDataLoading } = useGetSpotifyPlaylistQuery({ variables: { id } })
const { data: spotifyPlaylistSongsData, loading: spotifyPlaylistSongsLoading, fetchMore: fetchMoreSpotifyPlaylistSongs, variables: spotifyPlaylistSongsVariables } = useGetSpotifyPlaylistSongsQuery({ variables: { id, offset: 0, limit: 5 } })
const [spotifyPlaylistSongsJSON, setSpotifyPlaylistSongsJSON] = useState(null)
const [newOffset, setNewOffset] = useState(5)
const [playlistJSON, setPlaylistJSON] = useState(null)
const [isPublicIconFilled, setIsPublicIconFilled] = useState(false)
const [isCollaborativeIconFilled, setIsCollaborativeIconFilled] = useState(false)
useEffect(() => {
const cache = new InMemoryCache()
if (cache.evict({ fieldName: "getMoreSpotifyPlaylists", broadcast: false })) {
console.log('evicted')
} else {
console.log("error")
}
setRerender(r => !r)
}, [router.query.id, id]);
useEffect(() => {
if (playlistData?.getSpotifyPlaylist?.jsonData) {
setPlaylistJSON(JSON.parse(playlistData?.getSpotifyPlaylist?.jsonData))
}
if (spotifyPlaylistSongsData?.getSpotifyPlaylistSongs?.jsonData) {
setSpotifyPlaylistSongsJSON(JSON.parse(spotifyPlaylistSongsData?.getSpotifyPlaylistSongs?.jsonData))
}
}, [apolloClient, playlistData?.getSpotifyPlaylist?.jsonData, playlistDataLoading, spotifyPlaylistSongsData?.getSpotifyPlaylistSongs?.jsonData])
var showContent = useRef(false)
if (!playlistDataLoading) {
if (id && playlistData?.getSpotifyPlaylist?.jsonData) {
showContent.current = true
} else {
router.push("/")
}
}
if (showContent.current) {
return (
<AppContainer routeType='public' key={id}>
<LaHead title={menuTranslation('home')} />
<div className='w-full h-96 top-0 left-0 absolute z-0'>
<Image alt="test" layout='fill' objectFit='cover' src={playlistJSON?.images[0]?.url} />
<div className='w-full h-full top-0 left-0 absolute bg-gradient-to-t from-la-mauve1 dark:from-la-martinique3 to-transparent'></div>
<div className='w-full h-full top-0 left-0 absolute bg-la-mauve1/80 dark:bg-la-martinique3/80' />
</div>
<div className='z-10'>
<NavbarWithSearchBar />
<Container size='large'>
<LaBreadcrumb breadcrumbElements={[{
href: "/",
name: "Home",
isActive: false
}, {
href: "#",
name: playlistJSON?.name,
isActive: true
}]} />
<div className='flex items-center justify-between'>
<div className='flex items-center space-x-2 md:space-x-4'>
<Title>{playlistJSON?.name}</Title>
{playlistJSON?.public ?
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('public')} onMouseEnter={() => { setIsPublicIconFilled(true) }} onMouseLeave={() => { setIsPublicIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<UsersGroupIcon fillEffect={isPublicIconFilled} className='w-6 h-6' />
</span>
:
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('private')} onMouseEnter={() => { setIsPublicIconFilled(true) }} onMouseLeave={() => { setIsPublicIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<LockIcon fillEffect={isPublicIconFilled} className='w-6 h-6' />
</span>}
{playlistJSON?.collaborative ?
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('collaborative')} onMouseEnter={() => { setIsCollaborativeIconFilled(true) }} onMouseLeave={() => { setIsCollaborativeIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<UsersIcon fillEffect={isCollaborativeIconFilled} className='w-6 h-6' />
</span>
:
<span className='flex cursor-pointer grow justify-center hover:text-la-seance2 hover:dark:text-la-seance1' data-tip={commonTranslation('individual')} onMouseEnter={() => { setIsCollaborativeIconFilled(true) }} onMouseLeave={() => { setIsCollaborativeIconFilled(false) }}>
<ReactTooltip arrowColor="transparent" multiline place="top" className="la-tooltip" effect="solid" />
<UserIcon2 fillEffect={isCollaborativeIconFilled} className='w-6 h-6' />
</span>}
</div>
<Button onClick={async () => {
}} fullWidth={false} content={<RefreshIcon className='w-6 h-6' />} />
</div>
<div className='text-flex italic'>
{playlistJSON?.description}
</div>
{spotifyPlaylistSongsLoading ? <div className='flex w-full justify-center'>
<LaLoader />
</div> : spotifyPlaylistSongsJSON?.items.length > 0 ?
<>
<ThreeBoxesContainer>
{spotifyPlaylistSongsJSON?.items?.map((item: any) => <SongBox key={item.track.id} songData={item} />)}
{spotifyPlaylistSongsData && spotifyPlaylistSongsData.getSpotifyPlaylistSongs.hasMore ?
<Button onClick={async () => {
await fetchMoreSpotifyPlaylistSongs({ variables: { id, offset: newOffset, limit: spotifyPlaylistSongsVariables?.limit } })
setNewOffset(newOffset + spotifyPlaylistSongsVariables.limit)
}} content={commonTranslation('showmore')} /> : null}
</ThreeBoxesContainer>
</>
: <MessageBox type='warning' title={commonTranslation('error')} message={actionsTranslation('noPlaylists')} />}
</Container>
<BottomNavbar />
</div>
</AppContainer>
);
}
return (
<div><LoadingBar className="!bg-la-seance2 !dark:bg-la-seance1" progress={progress}
onLoaderFinished={() => setProgress(0)} /></div>
)
}

Cannot open modal when click the button

So, when i click the cart button, the modal should be open since i passed the openModal fuction down to the cart button, but when i click on the cart button, nothing happens, please help me to find out what wrong with my codes! Thank you so much!
context.js:
class ProductProvider extends React.Component {
state = {
products: storeProducts,
detailProduct: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return (
{ products: tempProducts, cart: [...this.state.cart, product] },
() => console.log(this.state)
);
});
};
openModal = (id) => {
const product = this.getItem(id);
this.setState(() => {
return { modalProduct: product, openModal: true };
});
};
closeModal = (id) => {
this.setState(() => {
return { modalOpen: false };
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
addToCart: this.addToCart,
openModal: this.openModal,
closeModal: this.closeModal
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
Product.js:
class Product extends React.Component {
render() {
const { id, title, img, price, inCart } = this.props.product;
return (
<ProductWrapper clasName="col-9 mx-auto col-md-6 col-lg-3 my-3">
<div className="card">
<ProductContext.Consumer>
{(value) => (
<div className="img-container p-5">
<Link to={`/details/${id}`}>
<img src={img} alt="product" className="card-img-top" />
</Link>
<button
className="cart-btn"
onClick={() => {
value.addToCart(id);
value.openModal(id);
}}
disabled={inCart ? true : false}
>
{inCart ? (
<p className="text-capitalize mb-0">In Cart</p>
) : (
<i class="fas fa-cart-plus"></i>
)}
</button>
</div>
)}
</ProductContext.Consumer>
<div className="card-footer d-flex justify-content-between">
<p className="align-self-center mb-0">{title}</p>
<h5 className="text-blue mb-0">
<span className="mr-1">$</span>
{price}
</h5>
</div>
</div>
</ProductWrapper>
);
}
}
Modal.js:
class Modal extends React.Component {
render() {
return (
<ProductConsumer>
{(value) => {
const { modalOpen, closeModal } = value;
const { img, title, price } = value.modalProduct;
if (!modalOpen) {
return null;
} else {
return (
<ModalContainer>
<div className="container">
<div className="row">
<div
className="col-8 mx-auto col-md-6 col-lg-4 p-5 text-center text-capitalize"
id="modal"
>
<h5>item added to cart</h5>
<img src={img} className="img-fluid" alt="" />
<h5>{title}</h5>
<h5 className="text-muted">price : ${price}</h5>
<Link to="/">
<ButtonContainer
onClick={() => {
closeModal();
}}
>
Continue Shopping
</ButtonContainer>
</Link>
<Link to="/cart">
<ButtonContainer
cart
onClick={() => {
closeModal();
}}
>
Go To Cart
</ButtonContainer>
</Link>
</div>
</div>
</div>
</ModalContainer>
);
}
}}
</ProductConsumer>
);
}
}
Sanbox link for better observation: https://codesandbox.io/s/why-cant-i-fetch-data-from-a-passed-value-forked-buz0u?file=/src/App.js

Cannot add item to cart when click on the button

When i click on the cart symbol, it changes its value to be in cart but actually my product is not rendered on the cart component, is there something wrong or missing in my code, please help me to fix it! Thank you so much!
Context.js:
class ProductProvider extends React.Component {
state = {
products: storeProducts,
detailProduct: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
addToCart = (id) => {
console.log("add to cart");
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return (
{ products: tempProducts, cart: [...this.state.cart, product] },
() => console.log(this.state)
);
});
};
openModal = (id) => {
const product = this.getItem(id);
this.setState(() => {
return { modalProduct: product, openModal: true };
});
};
closeModal = (id) => {
this.setState(() => {
return { modalOpen: false };
});
};
Cart.js:
import React from "react";
import CartColumns from "./CartColumns";
import CartList from "./CartList";
import EmptyCart from "./EmptyCart";
import { ProductContext } from "../App";
export default class Cart extends React.Component {
render() {
return (
<div>
<ProductContext.Consumer>
{(value) => {
console.log(value, "inside Product COnt");
if (value.length > 0) {
return (
<div>
<CartColumns />
<CartList items={items} />
</div>
);
} else {
<EmptyCart />;
}
}}
</ProductContext.Consumer>
</div>
);
}
}
CartList.js:
import React from "react";
import CartItem from "./CartItem";
export default function CartList(props) {
const { items } = props;
return (
<div>
{items.cart.map((item) => (
<CartItem
key={item.id}
item={item}
increment={item.increment}
decrement={item.decrement}
/>
))}
</div>
);
}
CartItem.js:
import React from "react";
function CartItem(props) {
const { id, title, img, price, total, count } = props.item;
const { increment, decrement, removeItem } = props;
return (
<div className="row my-1 text-capitalize text-center">
<div className="col-10 mx-auto col-lg-2">
<img
src={img}
style={{ width: "5rem", heigth: "5rem" }}
className="img-fluid"
alt=""
/>
</div>
<div className="col-10 mx-auto col-lg-2 ">
<span className="d-lg-none">product :</span> {title}
</div>
<div className="col-10 mx-auto col-lg-2 ">
<strong>
<span className="d-lg-none">price :</span> ${price}
</strong>
</div>
<div className="col-10 mx-auto col-lg-2 my-2 my-lg-0 ">
<div className="d-flex justify-content-center">
<div>
<span className="btn btn-black mx-1">-</span>
<span className="btn btn-black mx-1">{count}</span>
<span className="btn btn-black mx-1">+</span>
</div>
</div>
</div>
<div className="col-10 mx-auto col-lg-2 ">
<div className=" cart-icon">
<i className="fas fa-trash" />
</div>
</div>
<div className="col-10 mx-auto col-lg-2 ">
<strong>item total : ${total} </strong>
</div>
</div>
);
}
export default CartItem;
Sandbox link for better observation:https://codesandbox.io/s/cart-code-addict-forked-l6tfm?file=/src/cart/CartItem.js
There were few issues
Change following
this.setState(() => {
return (
{ products: tempProducts, cart: [...this.state.cart, product] },
() => console.log(this.state)
);
});
to
this.setState(
{
products: tempProducts,
cart: [...this.state.cart, product]
},
() => console.log(this.state)
);
And need to use value.cart to check for length and when passing as a prop.
Instead of
if (value.length > 0) {
return (
<div>
<CartColumns />
<CartList items={items} />
</div>
);
}
It should be
if (value.cart.length > 0) {
return (
<div>
<CartColumns />
<CartList items={value.cart} />
</div>
);
}
And in CartList,
Instead of
{
items.cart.map(item => (
<CartItem
key={item.id}
item={item}
increment={item.increment}
decrement={item.decrement}
/>
));
}
it should be
{
items.map(item => (
<CartItem
key={item.id}
item={item}
increment={item.increment}
decrement={item.decrement}
/>
));
}
Now it shows the cart with items
Code sandbox => https://codesandbox.io/s/cart-code-addict-forked-c3kug?file=/src/cart/CartList.js

Resources