How to use display slideshow items correctly in React? - arrays

This slideshow is rendering an array of items instead of individual items. I think I need to split up the array in order to get it right. see my link here: https://codesandbox.io/s/exciting-waterfall-gqekv?file=/slideshow2.js . My log shows that mapped items is an array, does that mean i need to use splice or spread somehow?
I need each slide to say one name, not the whole array on one slide. If I remove the DUMMY COMPONENT it works fine for some reason?
import React from "react";
import AliceCarousel from "react-alice-carousel";
import "react-alice-carousel/lib/alice-carousel.css";
const items = [{ name: "Bob" }, { name: "jill" }, { name: "wesker" }];
let mappeditems = items.map((item, index) => <div key={index}>{item}</div>);
console.log(mappeditems);
console.log(Array.isArray(mappeditems));
const Gallery = () => {
return (
<div>
<AliceCarousel mouseTracking items={items}>
{mappeditems}
<div>DUMMYCOMPONENT</div>
</AliceCarousel>
</div>
);
};
export default Gallery;

Related

Why when you delete an element out of react array, the inner element you pass it to remains

In a react component I have an array of things. I iterate through that array display the name of the thing in a plain div, then pass each element to another component to display details.
What's happening: if I delete an element from anywhere except the bottom (last array element) the header that is displayed in the main element containing the array is correct (the one I clicked "delete" on disappeared), but the "body" (which is another component) remains. Instead, the inner component is acting as if I deleted the last element of the array and kind of "moves" up the array.
It's hard to describe in words. See example below. Delete the top element or one of the middle ones and see how the header for the section starts not matching the contents.
I'm trying to understand why this is happening.
(EDIT/NOTE: State IS needed in the child component because in real life it's a form and updates the object being passed in. I Just removed the updating here to make the example shorter and simpler)
Example code (delete the middle element of the array and see what happens):
https://codesandbox.io/s/confident-buck-dodvgu?file=/src/App.tsx
Main component:
import { useState, useEffect } from "react";
import InnerComponent from "./InnerComponent";
import Thing from "./Thing";
import "./styles.css";
export default function App() {
const [things, setThings] = useState<Thing[]>([]);
useEffect(() => resetThings(), []);
const resetThings = () => {
setThings([
{ name: "dog", num: 5 },
{ name: "cat", num: 7 },
{ name: "apple", num: 11 },
{ name: "book", num: 1}
]);
};
const onDeleteThing = (indexToDelete: number) => {
const newThings = [...things];
newThings.splice(indexToDelete, 1);
setThings(newThings);
};
return (
<div className="App">
{things.map((thing, index) => (
<div key={`${index}`} className="thing-container">
<h2>{thing.name}</h2>
<InnerComponent
thing={thing}
index={index}
onDelete={onDeleteThing}
/>
</div>
))}
<div>
<button onClick={resetThings}>Reset Things</button>
</div>
</div>
);
}
Inner component:
import { useEffect, useState } from "react";
import Thing from "./Thing";
interface InnerComponentParams {
thing: Thing;
index: number;
onDelete: (indexToDelete: number) => void;
}
export const InnerComponent: React.FC<InnerComponentParams> = ({
thing,
index,
onDelete
}) => {
const [name, setName] = useState(thing.name);
const [num, setNum] = useState(thing.num);
return (
<div>
<div>Name: {name}</div>
<div>Num: {num}</div>
<div>
<button onClick={(e) => onDelete(index)}>Delete Me</button>
</div>
</div>
);
};
export default InnerComponent;
You are creating unnecessary states in the child component, which is causing problems when React reconciles the rearranged Things. Because you aren't setting the state in the child component, leave it off entirely - instead, just reference the prop.
export const InnerComponent: React.FC<InnerComponentParams> = ({
thing,
index,
onDelete
}) => {
return (
<div>
<div>Name: {thing.name}</div>
<div>Num: {thing.num}</div>
<div>
<button onClick={(e) => onDelete(index)}>Delete Me</button>
</div>
</div>
);
};
The other reason this is happening is because your key is wrong here:
{things.map((thing, index) => (
<div key={`${index}`}
Here, you're telling React that when an element of index i is rendered, on future renders, when another element with the same i key is returned, that corresponds to the JSX element from the prior render - which is incorrect, because the indicies do not stay the same. Use a proper key instead, something unique to each object being iterated over - such as the name.
<div key={thing.name}
Using either of these approaches will fix the issue (but it'd be good to use both anyway).
This is also wrong. You're removing everything except the index.
const onDeleteThing = (indexToDelete: number) => {
const newThings = [...things];
newThings.splice(indexToDelete, 1);
setThings(newThings);
};
Use filter:
const onDeleteThing = (indexToDelete: number) => {
const newThings = [...things].filter(
(thing, index) => index !== indexToDelete
);
setThings(newThings);
};

Only first element in a react.createElement is rendered

I need to render dynamicaly an img. Here is my function that translate my logos to components :
import {ReactComponent as MyLogo1} from '../assets/img/logo1.svg'
import {ReactComponent as MyLogo2} from '../assets/img/logo2.svg'
import {ReactComponent as MyLogo3} from '../assets/img/logo3.svg'
export const logoToComponent: any = {
logo_1: MyLogo1,
logo_2: MyLogo2,
logo_3: MyLogo3
};
and then I render it like this :
[...]
const logo: any = (key: string, props: any) => {
return logoToComponent[key] ? React.createElement(logoToComponent[key], props) : null;
};
return (
[...]
{logo(`logo_${category.code}`, { className: "mx-auto mb-3 w-6 h-6" })}
[...]
)
The problem is, only the first logo is rendered. In my DOM, when I inspect, every logos are there but only the first one is visible. And in my SVGs, the fill is set for each logos.
Any idea why React only render the first element ?
PS: If I delete the first line in my logoToComponent function, the second is visible and not the following ones.
I post the answer in case it can help.
Every SVG had the same id "clip-path". I just set a unique id in each SVG, and it works fine.

Need to re-render component after state is set to true

In my react app, I have a page that allows the user to "add" exercises to a personal list. The option to add an exercise is included in one "page" and the ability to see added exercises is view in a other. Side by side showing the two pages, notice 'Your Exercises' is empty. What I am trying to do is display these selected exercises. The exercises themselves are loaded via mapping function, from an array of objects. each object has a "selected" field and are set as "false" by default. My "add" button in the exercises page changes the state value of each component to "true", so now what I want to do is get these "true" values and render them on the page where it should show your exercises.
//Render your exercises
import React from "react";
import ExListItem from "./ExListItem";
// import selectedExercises from "../../../selectedExercises";
import exercises from "../../../exercises";
const selectedExerciseList = exercises
.filter(function (item) {
return item.selected === true;
})
.map(function ({name, reps}) {
return {name, reps};
});
// MAP HERE
function createSelectedExercises(exercises) {
return <ExListItem name={exercises.name} reps={exercises.reps} />;
}
function ExerciseList() {
return (
<ul data-tag="channelList" id="exercise-list" class="list-group mb-3">
{selectedExerciseList.map(createSelectedExercises)}
</ul>
);
}
export default ExerciseList;
Shouldnt this map the exercises?
You may need to use React state to accomplish this! You can do so with the following:
import React, { useState, useEffect } from 'react'
const Component = () => {
const [exercises, setExercises] = useState([])
useEffect(() => {
setExercises(exerciseFilterFunction(exercises)) // this will cause the re-render
}, [])
return (
<div>{exercises.map(e => <div>{e.name}</div>)}</div>
)
}

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

Map and render two different arrays in the same component - React

I just started learning React and I'm having issues understanding how to map and and render two different arrays in the same component. In my CMS I have 3 entries containing a title and a description each. I am calling them from GraphQL with the var data. The component SectionLeft has a grid container with threeFeatures as className. In my homepage I am passing the content to the grid container with the prop bottomcontent. I am passing the component SingleFeature in array which is my grid column and will contain an icon, a title and a description, it will repeat 3 times and compose my 3 column x 1 row grid. The component SingleFeature has the prop children which should contain an array map of the constant iconList containing 3 different svg icon components. I can't understand how to write a multiple array map for the component SingleFeature which will display the same data array displayed below for the props feature and details, but will render the 3 components for children in array. Any explanation would be really appreciated. Thank you.
homepage.js
import IconOne from "../components/icons/icon-one"
import IconTwo from "../components/icons/icon-two"
import IconThree from "../components/icons/icon-three"
export const iconList = [IconOne, IconTwo, IconThree]
export default function Home({data}) {
return (
<div>
<SectionLeft bottomcontent =
{data.allContentfulHomepageWordpressFeatures.edges.map(edge => (
<SingleFeature
children = {
...iconList array...
}
feature = {
edge.node.feature
}
details = {
edge.node.description
}
/>
))
}
/>
);
}
section-left.js
export default function SectionLeft(props) {
return (
<div className="threeFeatures">{props.bottomcontent}</div>
);
}
single-feature.js
export default function SingleFeature(props) {
return(
<div>
{props.children}
<h3>{props.feature}</h3>
<p>{props.details}</p>
</div>
);
}
GraphQL query
export const query = graphql`
allContentfulHomepageSpeedFeatures {
edges {
node {
feature
description
}
}
}
}
`
I would do it this way:
data.allContentfulHomepageWordpressFeatures.edges.map((edge, index) => (
<SingleFeature
children = {
iconList[index]
}
feature = {
edge.node.feature
}
details = {
edge.node.description
}
/>
))
map passes the index of the element in the mapped array as a second parameter to your arrow function.

Resources