How to add custom arrow buttons to Alice-Carousel? - reactjs

I am making a carousel component with alice-carousel (https://github.com/maxmarinich/react-alice-carousel/blob/master/README.md) but having trouble customising the arrows. Code as follows
export const Carousel: React.FC<CarouselProps> = ({text }) => {
const [activeIndex, setActiveIndex] = useState(0);
const items = ["item1","item2","item3"]
const slidePrev = () => {
activeIndex==0?(
setActiveIndex(items.length-1)):
setActiveIndex(activeIndex - 2);
};
const slideNext = () => {
activeIndex==items.length-1?(
setActiveIndex(0))
: setActiveIndex(activeIndex + 2)
};
return(
<div className="grid grid-cols-3">
<div className="col-span-2>
{text}
</div>
<div className="flex justify-end">
<button className="px-8" onClick={slidePrev}><ArrowL/></button>
<button className="px-8" onClick={slideNext}><ArrowR/></button>
</div>
<div className="col-span-3 px-10">
<AliceCarousel
mouseTracking
disableDotsControls
disableButtonsControls
activeIndex={activeIndex}
items={items}
responsive={responsive}
controlsStrategy="responsive"
autoPlay={true}
autoPlayInterval={5000}
infinite={true}
keyboardNavigation={true}
/>
</div>
</div>
)}
Above code changes the activeIndex therefore changing the items display order but it does so without the "slide" animation. I've looked at examples provided in the library used however cant seem to get it to slide smoothly. What am I doing wrong?

I encourage you to use the library's options to reduce the complexity of your implementation and stop unwanted behavior.
According to the documentation, there are two functions renderPrevButton and renderNextButton with the AliceCarousel to render your custom component (any elements, icons, buttons, ...) for the Prev and Next item on the gallery.
So, instead of defining a custom button with a custom action handler, pass your desired component to the mentioned function and give them some styles for customization.
export const Carousel: React.FC<CarouselProps> = ({text}) => {
const items = ["item1","item2","item3"]
return (
<div className="grid grid-cols-3">
<div className="col-span-2"> // ---> you forgot to add closing string quotation mark
{text}
</div>
<div className="col-span-3 px-10">
<AliceCarousel
mouseTracking
disableDotsControls
// disableButtonsControls // ---> also remove this
// activeIndex={activeIndex} // ---> no need to this anymore
items={items}
responsive={responsive}
controlsStrategy="responsive"
autoPlay={true}
autoPlayInterval={5000}
infinite={true}
keyboardNavigation={true}
renderPrevButton={() => {
return <p className="p-4 absolute left-0 top-0">Previous Item</p>
}}
renderNextButton={() => {
return <p className="p-4 absolute right-0 top-0">Next Item</p>
}}
/>
</div>
</div>
)
}
Note: you need to remove the disableButtonsControls option from the AliceCarousel to handle the custom buttons properly. also, you don't need to use the activeIndex option anymore since the carousel will handle them automatically.
As a sample, I passed a p element with my renderPrevButton without any onClick action. you can define your custom icon, image, or any element and passed them.

Hi for the renderNextButton/renderPrevButton you have to declare a function first, then pass that function to the render option of the Alice Carousel.
import ArrowBackIosIcon from '#mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '#mui/icons-material/ArrowForwardIos';
export const Carousel: React.FC<CarouselProps> = ({text}) => {
const items = ["item1","item2","item3"]
const renderNextButton = ({ isDisabled }) => {
return <ArrowForwardIosIcon style={{ position: "absolute", right: 0, top: 0 }} />
};
const renderPrevButton = ({ isDisabled }) => {
return <ArrowBackIosIcon style={{ position: "absolute", left: 0, top: 0 }} />
};
return (
<div className="grid grid-cols-3">
<div className="col-span-2"> // ---> you forgot to add closing string quotation mark
{text}
</div>
<div className="col-span-3 px-10">
<AliceCarousel
mouseTracking
disableDotsControls
// disableButtonsControls // ---> also remove this
// activeIndex={activeIndex} // ---> no need to this anymore
items={items}
responsive={responsive}
controlsStrategy="responsive"
autoPlay={true}
autoPlayInterval={5000}
infinite={true}
keyboardNavigation={true}
renderPrevButton={renderPrevButton}
renderNextButton={renderNextButton}
/>
</div>
</div>
)
}

Related

SlateJS React trying to get a textfield to work inside an Element

I have a Slate element that looks like this:
import React, {useState} from 'react'
import "./App.css"
export default function Accordion(props) {
const [closed, setClosed] = useState(false) //todo eventually fetch starting position from savefile
const [heightClass, setHeightClass] = useState("")
const handleToggle = () => {
if(closed === false){
setHeightClass("h-0")
}
else{
setHeightClass("")
}
setClosed(!closed)
}
return (
<>
<div {...props.attributes}>
{/* title and button */}
<div className='flex justify-between '>
<div className='font-semibold'>
<DefaultElement {...props}/> //the title of the accordion
</div>
<div
className={`px-2 cursor-pointer font-bold font-mono select-none ${closed ? "rotate-180" : ""}`}
onClick={() => {
handleToggle()
}}>
V
</div>
</div>
{/* ${closed ? "h-0" : ""} */}
<div className={`rounded border-l px-2 overflow-hidden accordionTransition `}
style={{height: closed ? "0px" : ""}}>
{props.children}
</div>
</div>
</>
)
}
const DefaultElement = props => {
console.log(props)
return <p {...props.attributes}>
{props.children}
</p>
}
Which is used by the Editor in App.js:
const App = () => {
const [editor] = useState(() => withReact(createEditor()))
// Define a rendering function based on the element passed to `props`. We use
// `useCallback` here to memoize the function for subsequent renders.
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'accordion':
return <Accordion {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
return (
<div className='p-5'>
<Slate editor={editor} value={initialValue}>
<Editable
// Pass in the `renderElement` function.
renderElement={renderElement}
/>
</Slate>
</div>
)
}
const DefaultElement = props => {
return <span {...props.attributes}>
{props.children}
</span>
}
export default App;
I'm trying to get the Accordion title to be properly editable and work as Slate intended. Just a a simple textfield. I can't figure out the correct syntax and how i'm supposed to do this.
When i do this, the title and the accordion content (props.children) are the same. I don't understand why.
If i remove the <DefaultElement {...props}/> and just write some text, it throws me this error when i edit it: Uncaught Error: Cannot resolve a Slate point from DOM point: [object Text],4

How to keep the active menu selected in React?

I have some menu items in a component and some styling added to the selected menu item. When I refresh the page, I want the selected menu to persist and not the initial state.
import ClaimData from "./Data";
const Services = () => {
const [tabIndex, setTabIndex] = useState(1);
const [selected, setSelected] = useState(1);
return (
<section >
<h3>
Menu
</h3>
<div>
{ClaimData.map((item, index) => {
return (
<div
key={index}
style={
selected === item.tabNum
? {
borderBottom: "3px solid green",
backgroundColor: "#E8E8E8",
}
: null
}
onClick={() => setSelected(item.tabNum)}
>
<p
onClick={() => setTabIndex(item.tabNum)}
style={{ color: item.color }}
>
<item.icon />
<span>{item.title}</span>
</p>
</div>
);
})}
</div>
<div>
{tabIndex === 1 && <MenuOneComponent />}
{tabIndex === 2 && <MenuTwoComponent />}
{tabIndex === 3 && <MenuThreeComponent />}
</div>
</section>
);
};
export default Services;
I have removed some codes for brevity. I would appreciate any help
To presist state on refresh you need to store the state outside of react.
Easiest would propbably be to use localStorage or sessionStorage. But it is of course possible to save it in a database/redis.
One way is to use url to determine which menu to highlight.

How Could I Change The State When Scroll on ReactJS?

I want to change the state when scrolling. Here, the default value of the background is false. I want when scroll the page the value will be true and then I will change the className dynamically using the ternary operator. But it isn't working. please give me a solution using onScoll method and the background state will be changed when scroll the page.
const Header = () => {
const [background, setBackground] = useState(false)
return (
<div onScroll={() => setBackground(true)} >
<div >
<div className={background ?
'nav bg-error w-[90%] py-5 mx-auto text-[white] flex justify-between '
:
'nav w-[90%] py-5 mx-auto text-[white] flex justify-between'}>
<div>
<p> logo</p>
</div>
<div>
<ul className='flex justify-around'>
<li>item1</li>
<li className='mx-10'>item2</li>
<li className='mr-10'>item3</li>
<li>item4</li>
</ul>
</div>
</div>
</div>
<div className=' text-[white]'>
<img src="https://img.freepik.com/free-photo/close-up-islamic-new-year-concept_23-2148611670.jpg?w=1380&t=st=1655822165~exp=1655822765~hmac=c5954765a3375adc1b56f2896de7ea8a604cd1fb725e53770c7ecd8a05821a60" alt="" />
</div>
</div>
);
};
export default Header;
That's because you need to set overflow:scroll to the div. Otherwise the onScroll prop won't work.
But remember that using the solution above will render unwanted extra scrollbars.
So you can try this instead:
const [background, setBackground] = React.useState(false);
React.useEffect(() => {
window.addEventListener("scroll", (e) => setBackground(true));
return () => {
window.removeEventListener("scroll", (e) => setBackground(false));
};
}, [background]);

Prevent mapped components in conditional from reloading?

I have a screen with two tabs/buttons "Recent" and "Top". When switching between the two, the individual items always reload the data which is unnecessary and causes shifting. I haven't been able to solve this with useMemo or anything similar.
How can I make the components not load their data again when switching between tabs:
Example editable in Sandbox: codesandbox
Edit: I realized I can use CSS to hide the non-active tab or I can load the data at a higher level to prevent the reload - but at the component level, is it possible to stop a reload of the data?
import * as React from "react";
import "./styles.css";
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
const Item = ({ title }) => {
const [itemLoading, setItemLoading] = React.useState(true);
async function loadItem() {
await delay((Math.random() * (3 - 1) + 1) * 1000);
setItemLoading(false);
}
React.useEffect(() => {
loadItem();
}, []);
if (itemLoading) {
return (
<div className="w-full text-center py-4 border bg-white rounded shadow-lg">
item is loading ...
</div>
);
} else {
return (
<div className="w-full text-center py-4 border bg-white rounded shadow-lg">
item {title}
</div>
);
}
};
export default function App() {
const [tab, setTab] = React.useState("Recent");
const recentItems = ["1", "2", "3", "4", "5", "6", "7", "9"];
const topItems = ["99", "98", "97", "96", "95", "94", "93"];
return (
<div className="w-screen h-screen bg-gray-100">
<h1 className="text-2xl text-center py-4">Current Tab: {tab}</h1>
<section className="w-full h-24 grid grid-cols-2">
<div
className="cursor-pointer text-center bg-red-200 pt-8"
onClick={() => {
setTab("Recent");
}}
>
Recent
</div>
<div
className="cursor-pointer text-center bg-yellow-200 pt-8"
onClick={() => {
setTab("Top");
}}
>
Top
</div>
</section>
<section>
{tab === "Recent"
? recentItems.map((item) => <Item key={item} title={item} />)
: topItems.map((item) => <Item key={item} title={item} />)}
</section>
</div>
);
}
Every time tab state changes, array.map is invoked and creates a new array of react components:
{tab === "Recent"
? recentItems.map((item) => <Item key={item} title={item} />)
: topItems.map((item) => <Item key={item} title={item} />)}
And everytime a new component is created, the code you've provided tries to fetch data with async call and ReactDOM re-renders the table. Data fetching part should be seperate from component if speed is a constraint.
You can achive that by either moving the data state up to upper component(in your case App) or handle it seperately elsewhere. First option is recommended.
The way I solved this without moving the data loading further up, is to set the height and width of the tabs to 0, and mark them as hidden (unless it it the current tab). This got rid of all double loading and cumulative layout shift without modify lots of code.
(tailwind)
const hiddenClass = "w-0 h-0 hidden"
<div className={currentTab === "Recent" ? undefined : hiddenClass} >
...recent items
</div>
<div className={currentTab === "Popular" ? undefined : hiddenClass} >
...popular items
</div>

React: Muuri-react generate random items error - Uncaught Invariant: Invariant failed: The item has not been setted yet

The muuri-react demonstrates to generate and add random items.
There was a small modification on the codes to randomly generate 3 items rather than adding 3 more random items. When I modified in codepen and ran it, the items could render successfully. However, when I tried it in a local machine, it couldn't.
When I clicked the "Generate item" button, nothing was appearing on the div, and after the second click, an error message "Uncaught Invariant: Invariant failed: The item has not been setted yet at invariant" shown on the console.
Could anyone tell me what goes wrong?
The following is the code:
import { useFilter, generateItems, options } from "./utils";
import { MuuriComponent } from "muuri-react";
import './style.css'
function MuuriDemo() {
const [items, setItems] = useState(generateItems());
const Item = ({ color, width, height, title, remove }) => {
console.log(color);
return (
<div className={`item h${height} w${width} ${color}`}>
<div className="item-content">
<div className="card">
<div className="card-title">{title}</div>
<div className="card-remove">
<i className="material-icons" onMouseDown={remove}>

</i>
</div>
</div>
</div>
</div>
);
};
// Children.
const children = items.map(({ id, color, title, width, height }) => (
<Item
key={id}
color={color}
title={title}
width={width}
height={height}
/>
));
return (
<div>
<button onClick={() => setItems(generateItems())}>Generate item</button>
<section className="grid-demo">
<MuuriComponent
{...options}
propsToData={({ color, title }) => ({ color, title })}
>
{children}
</MuuriComponent>
</section>
</div>
)
}
export default MuuriDemo```
I have managed to get it works via the following code. I have no idea whether it is a standard way.
import { generateItems, options } from "./utils";
import { MuuriComponent } from "muuri-react";
import './style.css'
import { render } from "react-dom";
function MuuriDemo() {
const [items, setItems] = useState(generateItems());
const Item = ({ color, width, height, title, remove }) => {
console.log(color);
return (
<div className={`item h${height} w${width} ${color}`}>
<div className="item-content">
<div className="card">
<div className="card-title">{title}</div>
<div className="card-remove">
<i className="material-icons" onMouseDown={remove}>

</i>
</div>
</div>
</div>
</div>
);
};
// Children.
const children = items.map(({ id, color, title, width, height }) => (
<Item
key={id}
color={color}
title={title}
width={width}
height={height}
/>
));
useEffect(() => {
render(<div>
<button onClick={() => setItems(generateItems())}>Generate item</button>
<section className="grid-demo">
<MuuriComponent
{...options}
propsToData={({ color, title }) => ({ color, title })}
>
{children}
</MuuriComponent>
</section></div>, document.getElementById('muuri'))
console.log(children);
}, [children])
return (
<div id='muuri'/>
)
}
export default MuuriDemo

Resources