Possible to render components in reverse manner in JSX? - reactjs

In my Nextjs/React.js component I am rendering list of cards like this :
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
<div className="lg:col-span-8 col-span-1">
{posts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}
</div>
I was wondering if it was possible to render these PostCards in a reverse manner; starting from the last index rather than the initial index? This is for my blog application and whenever I post a new PostCard I want the latest Post to be rendered on Top of the list instead of the bottom of the list.

Just reverse the array first:
{posts.slice(0).reverse().map((post, index) => (
<PostCard
post={ post.node }
key={ post.title }
/>
))}

whenever I post a new PostCard I want the latest Post to be rendered on Top of the list instead of the bottom of the list.
If you're currently doing this by adding to the end of an array, you can add to the start of it instead. For example, if you're doing this:
setPosts(prev => [...prev, { title: 'My New Post' }]);
Do this instead:
setPosts(prev => [{ title : 'My New Post' }, ...prev]);
If you can't change the way the array gets created (say, because some components want it in one order, and some in another), then you can create a new array in the right order, and then map over that. You may want to memoize this if the array isn't going to change often:
const reorderedPosts = useMemo(() => {
return [...posts].reverse();
}, [posts]);
// ...
{reorderedPosts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}
This can also easily be enhanced to let you change the order via a state, if you need to:
const [shouldReverse, setShouldReverse] = useState(false);
const reorderedPosts = useMemo(() => {
if (shouldReverse) {
return [...posts].reverse();
} else {
return posts;
}
}, [posts, shouldReverse])
// ...
{reorderedPosts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}

Related

scroll to div using useRef in map method

i am using useRef to scroll to specific div but it is not working in map method's case (probably because of id) , so can anyone tell me how to provide id. it is taking last element in map method right now.
this is element to which i want to scroll to.
{allMessages?.map((message) => (
<div
key={message.data.id}
ref={filterRef}>
<div>
<p>{message.data.text}</p>
</div>
</div>
))}
this is filtered data in which i am getting filtered messages and clicks on specific div.
{filteredMsg?.map((item) => (
<li
onClick={() => goToFilterData(item.data.id)}
key={item.data.id}
>
{item.data.text}
</li>
))}
this is what i have done with useRef yet -
const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop);
const goToFilterData = (id) => {
scrollToRef(filterRef);
};
Just pass an id to every element that maybe should scroll into the view.
{allMessages?.map((message) => (
// the element you want to scroll to
<div id={`message_${message.someUniqueIdentifier}`} />
))}
Pass the identifier to the scroll function.
{filteredMsg?.map((item) => (
<li onClick={() => goToFilterData(item.data.someUniqueIdentifier)}>
{item.data.text}
</li>
))}
Query the element and make it scroll into the view.
const goToFilterData = (uniqueIdentifier) => {
const element = document.getElementById('message_' + uniqueIdentifier);
element.scrollIntoView()
};
Note: ofc you could handle this with a lot of single refs and pass them, but this should just work fine.

Removing a specific element in an array with React

I have this code
I have some sections and inside it I have an item, number and button. How can I remove some specific item?
I'm rendering the sections as:
{sections.map((section, index) => (
<Section
section={section}
key={section.id}
addItem={(item) => addItem(index, item)}
removeItem={(i) => removeItem(index, i)}
/>
))}
And in my Section component, I'm rendering it as:
{section.items.map((item, i) => (
<>
<h2>{item}</h2>
<h3>{section.number[i]}</h3>
<button onClick={() => removeItem(i)}>Remove</button>
</>
))}
The remove function is in the Section's parent component and is:
const removeItem = (index, i) => {
let filteredItem = sections[index].items.splice(i);
let filteredNumber = sections[index].number.splice(i);
setSections((prev) => ({ ...prev, filteredItem, filteredNumber }));
};
When I click the remove button it says that sections.map is not a function. What am I doing wrong?
At glance looks like you have an error in your Section component. The code:
{section.items.map((item, i) => (
<>
<h2>{item}</h2>
<h3>{section.number[i]}</h3>
<button onClick={() => removeItem(i)}>Remove</button>
</>
))}
should be:
{section.items.map((item, i) => (
<>
<h2>{item.title}</h2>
<h3>{item.number[i]}</h3>
<button onClick={() => removeItem(i)}>Remove</button>
</>
))}
Because you're passing down item and you're trying to render everything in the item object and section.number should be item.number.
I see your problem.
In the code below when removing an element you are setting the sections to a single object not an array anymore. so map() doesn't exist on an Object. You have to convert it back into an Array.
const removeItem = (index, i) => {
let filteredItem = sections[index].items.splice(i);
let filteredNumber = sections[index].number.splice(i);
setSections((prev) => ({ ...prev, filteredItem, filteredNumber }));
};
Edit:
Upon further inspection of your code I see more errors.
In the remove item section in Section.js i see your trying to
const itemTarget = section.items[i];
That seems to be cause you are acting as is section is one object. but its an array that already has one section so you have to call it as follows for it to grab the items from the first (default) section.
const itemTarget = section[0].items[i];
This is the same with the filtered variable you will have to make sure when removing the item you are removing it from the correct section aswell.

Accessing a component state from a sibling button

I'm building a page that will render a dynamic number of expandable rows based on data from a query.
Each expandable row contains a grid as well as a button which should add a new row to said grid.
The button needs to access and update the state of the grid.
My problem is that I don't see any way to do this from the onClick handler of a button.
Additionally, you'll see the ExpandableRow component is cloning the children (button and grid) defined in SomePage, which further complicates my issue.
Can anyone suggest a workaround that might help me accomplish my goal?
const SomePage = (props) => {
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { /* Need to access MyGrid state */ }} />
Add Row
</button>
<MyGrid>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
const ExpandableRowsComponent = (props) => {
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
return (
<>
{data.map((dataItem) => (
<ExpandableRow id={dataItem.id} />
))}
</>
);
};
const ExpandableRow = (props) => {
const [expanded, setExpanded] = useState(false);
return (
<div className="row-item">
<div className="row-item-header">
<img
className="collapse-icon"
onClick={() => setExpanded(!expanded)}
/>
</div>
{expanded && (
<div className="row-item-content">
{React.Children.map(props.children, (child => cloneElement(child, { id: props.id })))}
</div>
)}
</div>
);
};
There are two main ways to achieve this
Hoist the state to common ancestors
Using ref (sibling communication based on this tweet)
const SomePage = (props) => {
const ref = useRef({})
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { console.log(ref.current.state) }} />
Add Row
</button>
<MyGrid ref={ref}>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
Steps required for seconds step if you want to not only access state but also update state
You must define a forwardRef component
Update ref in useEffect or pass your API object via useImerativeHandle
You can also use or get inspired by react-aptor.
⭐ If you are only concerned about the UI part (the placement of button element)
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
(Mentioned point by #Sanira Nimantha)

React multi carousel renders items wrongly

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())} />

How to avoid React Hook UseState to share the states?

I may have a bad title for this question, but here's my situation.
I use a chunk of json to render a list. The list item can be expanded and showed the sub list if it has children property. The json structure includes two arrays and each array contains more sub-arrays. I use tabs to switch arrays.
I use useState to manage the value isExpanded of each individual sub-array component. but it seems like the state isExpaned is shared for all tabs.
The state isExpanded remains same even if I switch to another tab. In other words, why the sub-list keep expanded when I switch to another tab?
In addition, why the expanded sub-list of each tab overlaps each other. They should keep 'close' when I switch to another tab because I set the initial state to false already. (const [isExpand, setIsExpand] = useState(false))
const ListItem = ({name, children}) => {
const [subList, setSubList] = useState(null)
const [isExpand, setIsExpand] = useState(false)
const handleItemClick = () => {
children && setIsExpand(!isExpand)
console.log(isExpand)
}
useEffect(() => {
isExpand && children && setSubList(children)
}, [isExpand, children])
return (
<div className='list-wrapper'>
<div className='list-item'>
{name}
{
children &&
<span
className='expand'
onClick={() => handleItemClick()}>
{isExpand ? '-' : '+'}
</span>
}
</div>
<div className='list-children'>
{
isExpand && subList && subList.map((item, index) =>
<ListItem key={index} name={item} />
)
}
</div>
</div>
)
}
Here's the codesanbox, anyone helps?
It seems like React is confused due to index being used as ListeItem key.
(React will try to "share" isExpanded state as they look the same according to the key you specified)
You could change the key from key={index}
<div className="contents">
{contents &&
contents.children &&
contents.children.map((item, index) => (
<ListItem
...... 👇 ....
key={index}
name={item.name}
children={item.children}
/>
))}
</div>
to use more distinct key, item.name
<div className="contents">
{contents &&
contents.children &&
contents.children.map(item => (
<ListItem
...... 👇 ....
key={item.name}
name={item.name}
children={item.children}
/>
))}
</div>
Check out the forked sandbox.
https://codesandbox.io/s/soanswer57212032-9ggzj

Resources