React multi carousel renders items wrongly - reactjs

I started using Next js and I don't know whether it is problem regarding to it or React itself.
So the problem is that the "react-multi-carousel" does not work in my app. So, basically it works if I hardcode the values in there, but when I use my custom components, where the is map function, it does not render it properly. It takes 3 components as they are in one . You can verify it on the image I posted below. I tried to render Compilations component outside SliderCarousel component and it worked as it should, but when I pass Compilations as a child to SliderCarousel, it does not catch it and give it its own classes from react-multi-carousel library
Here is my code below and I ommited some imports and exports to focus attention on main parts
My Compilation component looks like this:
const compilation = ({ className, text, img }) => {
return (
<div className={`${className} ${classes.Compilation}`}>
<img src={img} alt={text} />
<div>
<h3>{text}</h3>
</div>
</div>
);
};
My Compilations component looks like this:
const compilations = ({ items, onClick }) => {
const compilationsView = items.map(item => {
return <Compilation key={item.id} onClick={() => onClick(item.id)} text={item.text} img={item.img} />;
});
return <React.Fragment>{compilationsView}</React.Fragment>;
};
SliderCarousel component looks like this
<Carousel
swipeable={true}
draggable={true}
showDots={true}
responsive={responsive}
ssr={true} // means to render carousel on server-side.
infinite={true}
autoPlay={true}
autoPlaySpeed={1000}
keyBoardControl={true}
customTransition="all .5"
transitionDuration={500}
containerClass="carousel-container"
removeArrowOnDeviceType={[ "tablet", "mobile" ]}
// deviceType={this.props.deviceType}
dotListClass="custom-dot-list-style"
itemClass="carousel-item-padding-40-px"
>
{items}
</Carousel>{" "}
Here is my pages/index.js file
<SliderCarousel items={<Compilations items={getBookCarouselItems()} />} />
And the function is:
{
id: 0,
img: "/static/images/main/books/slider-carousel/1.png",
text: "ТОП-10 романов"
},
{
id: 1,
img: "/static/images/main/books/slider-carousel/2.png",
text: "На досуге"
},
{
id: 2,
img: "/static/images/main/books/slider-carousel/3.png",
text: "Бестселлеры"
}
];
I hope that you can help me resolve this problem, cause I have no idea how to resolve this issue

actually this carousel makes a <li> for each element to manoeuvre the carousel effects as you can see in the inspect screenshot
in your code
const compilations = ({ items, onClick }) => {
const compilationsView = items.map(item => {
return <Compilation key={item.id} onClick={() => onClick(item.id)} text={item.text} img={item.img} />;
});
return <React.Fragment>{compilationsView}</React.Fragment>;
};
you are wrapping your map list in fragment and hence carousel got only one item as a component and hence one <li/>
so in order to work you'll have to pass the map list (i.e. array) of <Compilation />
const allCompilations = (items) => items.map(item => {
return <Compilation key={item.id} onClick={() => onClick(item.id)} text={item.text} img={item.img} />;
});
to you carousel as children
<SliderCarousel items={allCompilations(getBookCarouselItems())} />

Related

Render a React component by mapping an array inside of an array of objects

I have trouble rendering a react component by mapping an array nested inside an array of objects.
I have this React component, which is simple images
function ProjectLangIcon(props) {
return (
<img className="project-icon" src={props.src} alt={props.alt}/>
)
}
This component is a part of this bigger one :
function Project(props) {
const projectLang = projectList.map(projects => {
return projects.lang.map(langs => {
return <ProjectLangIcon
key={langs.key}
src={langs.src}
alt={langs.alt}
/>
})
})
return (
<div className="project">
{props.title}
<div className="project-content">
{projectLang}
</div>
</div>
)
}
I'm mapping the data from this file :
const projectList = [
{
key: 1,
name: "Site de mariage from scratch",
link: "https://mariage-hugo-et-noemie.fr/view/index.php",
lang: [css, html, javascript, php, mySql]
},
{
key: 2,
name: "Site de paris sportifs",
link: "https://akkezxla.ae.lu/Akkezxla-Betting-Site/bet-list.php",
lang: [css, html, javascript, php, mySql]
},
{
key: 3,
name: "Site de location d'appartement",
link: "http://paris-island.com/",
lang: [wordpress, css, javascript]
}
]
with the lang array being constitued of objects like these :
const css = {
name: "CSS",
src: cssIcon,
alt: "Icône CSS"
}
const html = {
name: "HTML",
src: htmlIcon,
alt: "Icône HTML"
}
Then, I map the projects component inside the Realisation component like this :
function Realisation() {
const projects = projectList.map(item => {
return <Project
key={item.key}
title={item.name}
link={item.link}
/>
})
return (
<div className="realisations pro-block">
<BlockTitle
title="Réalisations"
close={closeReal}
/>
<div className="realisation-content">
{projects}
</div>
<BlockNav
left="Compétences"
switchl={switchComp}
center="Parcours Porfessionnel"
switchc={switchParcoursPro}
right="Parcours Académique"
switchr={switchParcoursAcad}
/>
</div>
)
}
the result I get is this : enter image description here
But I want each projects to have its corresponding language icons.
Does anyone have an idea of hiw I should proceed ?
Your issue is due to Project component. You are rendering this component for each Project of projectList but inside of this component you are not taking in the account the actual Project that is passed to this component.
Project (component)
const projectLang = projectList.map(projects => {
return projects.lang.map(langs => {
return <ProjectLangIcon
key={langs.key}
src={langs.src}
alt={langs.alt}
/>
})
})
Lines above are building the same projectLang for any Project you passed to this component. And is building it from All the projects you have, it returns the array of arrays. So [[all_icons_of_project_1], [all_icons_of_project_2], ...].
Here is what you should do:
First: You need to modify Product component to either pass a product item itself, either to pass additional property, langs in your case. And to modify .map function a little.
Notice: i destructured the props object passed as a parameter to Product component. Also, i wrapped the map function into useMemo for optimization purposes.
function Project({ title, link, langs }) {
const projectLang = useMemo(() => {
return langs.map((lang) => (
<ProjectLangIcon key={lang.key} src={lang.src} alt={lang.alt} />
));
}, [langs]);
return (
<div className="project">
<a href={link} className="project-txt" target="_blank">
{title}
</a>
<div className="project-content">{projectLang}</div>
</div>
);
}
Second - just pass the langs attribute (prop) to your Product component. (Again, i wrapped it into useMemo, for optimizations)
function Realisation() {
const projects = useMemo(() => {
return projectList.map((item) => (
<Project
key={item.key}
title={item.name}
link={item.link}
langs={item.lang}
/>
));
}, []);
return (
<div className="realisations pro-block">
{/* ... */}
<div className="realisation-content">{projects}</div>
{/* ... */}
</div>
);
}
Note - no icons in codesandbox, only placeholders and alt text.

Is it possible to use the map function on an array of jsx items?

I am new to React and not entirely sure how to go about setting some things up:
I have a top navigation menu which turns into a hamburger menu for mobile devices. In order to define menu items only once, I thought it would be a good idea to extracted them into an array of jsx items and use the array for both types of menus:
export default function Navigation () {
const MenuItems = [
<Link to="/">Home</Link>,
<Link to="/page1">Page 1</Link>,
<Link to="/page2">Page 2</Link>,
<Link to="/page3">Page 3</Link>
];
const isMobile = useMediaQuery({ query: '(max-width: 767px)' });
const isDesktop = useMediaQuery({ query: '(min-width: 1224px)' });
return (
<Nav role='navigation'>
{ isMobile && <BurgerMenu MenuItems={ MenuItems } />}
{ isDesktop &&
<ul>
{ MenuItems.map((item, index) => <li key={index}>{ item }</li>) }
</ul>
}
</Nav>
);
}
I'm using react-burger-menu for the hamburger menu:
export default function BurgerMenu({ MenuItems }) {
const [menuOpen, setMenuOpen] = useState(false);
const handleClick = () => { setMenuOpen(false); };
return(
<Menu
isOpen={ menuOpen }
onStateChange={({isOpen}) => setMenuOpen(isOpen)}
right
width={ '254px' }
customBurgerIcon={ <img src={ burger } /> }
customCrossIcon={ <img src={ close } /> }
styles={ styles }
>
{ MenuItems.map(( item ) => item ) }
</Menu>
);
}
The problem is that because there's no onClick event handler on the menu items inside the burger menu, it never closes when clicking on one of the menu items. Since this is an array of jsx items I can't directly modify the items using the map function.
Is there a way to use my array and somehow inject the click event handler into each item (without converting it to a string)? Turning it into its own component would not allow me to then wrap each item with an li tag for desktop display.
I purposefully tried to avoid using an array of objects for this, but seems like this might be the best way to solve my problem?
I'm mainly just trying to understand if there are ways to approach this which I haven't thought about. Any insights or recommendations would be appreciated.
I would recommend taking a slightly different approach. Put data about your menu items in the array, and only create the components when you actually map over them. This would also give you some additional flexibility going forward.
function Navigation() {
const MenuItems = [
{
to: "/",
title: "Home",
},
{
to: "/page1",
title: "Page 1",
},
{
to: "/page2",
title: "Page 2",
},
{
to: "/page3",
title: "Page 3",
}
];
const isMobile = useMediaQuery({ query: '(max-width: 767px)' });
const isDesktop = useMediaQuery({ query: '(min-width: 1224px)' });
return (
<Nav role='navigation'>
{isMobile && <BurgerMenu MenuItems={MenuItems} />}
{isDesktop &&
<ul>
{MenuItems.map((item, index) => <li key={index}><Link to={item.to}>{item.title}</Link></li>)}
</ul>
}
</Nav>
);
}
This gives you the flexibility to add additional props to your items in the BurgerMenu component:
function BurgerMenu({ MenuItems }) {
const [menuOpen, setMenuOpen] = useState(false);
const handleClick = () => { setMenuOpen(false); };
return (
<Menu
isOpen={menuOpen}
onStateChange={({ isOpen }) => setMenuOpen(isOpen)}
right
width={'254px'}
customBurgerIcon={<img src={burger} />}
customCrossIcon={<img src={close} />}
styles={styles}
>
{MenuItems.map((item) => <Link to={item.to} onClick={handleClick}>{item.title}</Link>)}
</Menu>
);
}
I noticed this in your question
I purposefully tried to avoid using an array of objects for this
But I'm not sure I understand the reasoning behind it. I think this is the best approach here, and might go so far to say it's the best approach most of the time.
Have you tried wrapping the items with divs and adding onClick to them?
{ MenuItems.map(( item ) => <div onClick={handleClick}>{item}</div> ) }

Adding onClick prop to children using cloneElement

I'm trying to create an image gallery component which displays small thumbnail images and a larger image 'preview'. The preview is replaced with the currently selected thumbnail.
The component allows any number of thumbnails to be passed in as children, and then an onClick prop is added to each thumbnail using cloneElement. This is working fine and I can see the new prop in the console, but nothing happens once the image is clicked. Here's my code so far:
Parent
<Gallery>
{gallery.map((thumbnail) => {
return (
<Image fluid={thumbnail.asset.fluid} />
);
})}
</Gallery>
Child
const Gallery = (props) => {
const [thumb, setThumb] = useState({
preview: null,
});
const thumbnails = React.Children.map(props.children, (child) =>
React.cloneElement(child, {
onClick: () => {
console.log("Clicked!")
setThumb({
preview: child.asset.fluid,
});
},
})
);
return (
<section>
<div className={preview}>
<Image fluid={preview} />
</div>
<div className={thumbnails}>
{thumbnails}
</div>
</section>
);
};
export default Gallery;
I'm not sure why I'm not getting any response (even in the console) when the thumbnails are clicked.
This is my first time using React so apologies if this is a terrible method, please let me know if there's a simpler/better way.
I was able to solve this issue by wrapping the <Image> component in a <div> as recommended by Nadia Chibrikova:
Parent
<Gallery>
{gallery.map((thumbnail) => {
return (
<div>
<Image fluid={thumbnail.asset.fluid} />
</div>
);
})}
</Gallery>

How to render based on a condition in Typescript?

Say we have a component, with the following definition:
export const MentForm: React.FC<IProps> = (props: IProps) => {
It's return is a div with a load of HTML, some of which is the following:
<div className="form">
<MentFormButton {...fProps} />
<FlyComp
user={props.user}
onClick={props.onShowFly(props.user)}
/>
{showFlyForm (
<FlyForm
onFlyed={() => {
if (props.onFlyClicked) {
props.onFlyClicked(flies)
}
setShowFlyForm(false)
}}
onFlyFail={() => {
setShowFlyForm(false)
}}
user={flies}
/>
)}
</div>
Now, when we click the button, onShowFly gets triggered
We have another TSX file that, within a Test functional component, there is a return with a <div> and that has the following:
<MentForm
user={new User}
onCancelClick={() => {
if (props.user) {
wait()
}
}}
onShowFly={() => {// HERE //}}
/>
Where I've wrote "HERE", I want to be able to have the stuff in curly braces in the MentForm component to activate.... but I don't know how to do this....
Within the component that has your showFlyForm, you can use useEffect to fire off a function whenever the state changes and then just check for showFlyForm === true or whichever logic you use to check for showing the form.
Ex:
useEffect(() => {
showFlyForm && [your HERE function]
}, [showFlyForm])

React wrapper component (HOC) does not re-render child component when it's props are changing

My wrapper component has this signature
const withReplacement = <P extends object>(Component: React.ComponentType<P>) =>
(props: P & WithReplacementProps) => {...}
Btw, full example is here https://codepen.io/xitroff/pen/BaKQNed
It's getting original content from argument component's props
interface WithReplacementProps {
getContent(): string;
}
and then call setContent function on button click.
const { getContent, ...rest } = props;
const [ content, setContent ] = useState(getContent());
I expect that content will be replaced everywhere (1st and 2nd section below).
Here's the part of render function
return (
<>
<div>
<h4>content from child</h4>
<Component
content={content}
ReplaceButton={ReplaceButton}
{...rest as P}
/>
<hr/>
</div>
<div>
<h4>content from wrapper</h4>
<Hello
content={content}
ReplaceButton={ReplaceButton}
/>
<hr/>
</div>
</>
);
Hello component is straightforward
<div>
<p>{content}</p>
<div>
{ReplaceButton}
</div>
</div>
and that's how wrapped is being made
const HelloWithReplacement = withReplacement(Hello);
But the problem is that content is being replaced only in 2nd part. 1st remains untouched.
In the main App component I also replace the content after 20 sec from loading.
const [ content, setContent ] = useState( 'original content');
useEffect(() => {
setTimeout(() => {
setContent('...too late! replaced from main component');
}, 10000);
}, []);
...when I call my wrapped component like this
return (
<div className="App">
<HelloWithReplacement
content={content}
getContent={() => content}
/>
</div>
);
And it also has the issue - 1st part is updating, 2nd part does not.
It looks like you are overriding the withReplacement internal state with the external state of the App
<HelloWithReplacement
content={content} // Remove this to stop overriding it
getContent={() => content}
/>
Anyway it looks weird to use two different states, it is better to manage your app state in only one place

Resources